// Copyright (C) 2020-2023 Free Software Foundation, Inc.

// This file is part of GCC.

// GCC is free software; you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 3, or (at your option) any later
// version.

// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
// for more details.

// You should have received a copy of the GNU General Public License
// along with GCC; see the file COPYING3.  If not see
// <http://www.gnu.org/licenses/>.

#include "rust-compile-expr.h"
#include "rust-compile-struct-field-expr.h"
#include "rust-hir-trait-resolve.h"
#include "rust-hir-path-probe.h"
#include "rust-hir-type-bounds.h"
#include "rust-compile-pattern.h"
#include "rust-compile-resolve-path.h"
#include "rust-compile-block.h"
#include "rust-compile-implitem.h"
#include "rust-constexpr.h"
#include "rust-gcc.h"

#include "fold-const.h"
#include "realmpfr.h"
#include "convert.h"
#include "print-tree.h"

namespace Rust {
namespace Compile {

CompileExpr::CompileExpr (Context *ctx)
  : HIRCompileBase (ctx), translated (error_mark_node)
{}

tree
CompileExpr::Compile (HIR::Expr *expr, Context *ctx)
{
  CompileExpr compiler (ctx);
  expr->accept_vis (compiler);
  return compiler.translated;
}

void
CompileExpr::visit (HIR::TupleIndexExpr &expr)
{
  HIR::Expr *tuple_expr = expr.get_tuple_expr ().get ();
  TupleIndex index = expr.get_tuple_index ();

  tree receiver_ref = CompileExpr::Compile (tuple_expr, ctx);

  TyTy::BaseType *tuple_expr_ty = nullptr;
  bool ok
    = ctx->get_tyctx ()->lookup_type (tuple_expr->get_mappings ().get_hirid (),
				      &tuple_expr_ty);
  rust_assert (ok);

  // do we need to add an indirect reference
  if (tuple_expr_ty->get_kind () == TyTy::TypeKind::REF)
    {
      tree indirect = indirect_expression (receiver_ref, expr.get_locus ());
      receiver_ref = indirect;
    }

  translated
    = ctx->get_backend ()->struct_field_expression (receiver_ref, index,
						    expr.get_locus ());
}

void
CompileExpr::visit (HIR::TupleExpr &expr)
{
  if (expr.is_unit ())
    {
      translated = ctx->get_backend ()->unit_expression ();
      return;
    }

  TyTy::BaseType *tyty = nullptr;
  if (!ctx->get_tyctx ()->lookup_type (expr.get_mappings ().get_hirid (),
				       &tyty))
    {
      rust_fatal_error (expr.get_locus (),
			"did not resolve type for this TupleExpr");
      return;
    }

  tree tuple_type = TyTyResolveCompile::compile (ctx, tyty);
  rust_assert (tuple_type != nullptr);

  // this assumes all fields are in order from type resolution
  std::vector<tree> vals;
  for (auto &elem : expr.get_tuple_elems ())
    {
      auto e = CompileExpr::Compile (elem.get (), ctx);
      vals.push_back (e);
    }

  translated
    = ctx->get_backend ()->constructor_expression (tuple_type, false, vals, -1,
						   expr.get_locus ());
}

void
CompileExpr::visit (HIR::ReturnExpr &expr)
{
  auto fncontext = ctx->peek_fn ();

  std::vector<tree> retstmts;
  if (expr.has_return_expr ())
    {
      tree compiled_expr = CompileExpr::Compile (expr.return_expr.get (), ctx);
      rust_assert (compiled_expr != nullptr);

      retstmts.push_back (compiled_expr);
    }

  auto s = ctx->get_backend ()->return_statement (fncontext.fndecl, retstmts,
						  expr.get_locus ());
  ctx->add_statement (s);
}

void
CompileExpr::visit (HIR::ArithmeticOrLogicalExpr &expr)
{
  auto op = expr.get_expr_type ();
  auto lhs = CompileExpr::Compile (expr.get_lhs (), ctx);
  auto rhs = CompileExpr::Compile (expr.get_rhs (), ctx);

  // this might be an operator overload situation lets check
  TyTy::FnType *fntype;
  bool is_op_overload = ctx->get_tyctx ()->lookup_operator_overload (
    expr.get_mappings ().get_hirid (), &fntype);
  if (is_op_overload)
    {
      auto lang_item_type
	= Analysis::RustLangItem::OperatorToLangItem (expr.get_expr_type ());
      translated = resolve_operator_overload (lang_item_type, expr, lhs, rhs,
					      expr.get_lhs (), expr.get_rhs ());
      return;
    }

  if (ctx->in_fn () && !ctx->const_context_p ())
    {
      auto receiver_tmp = NULL_TREE;
      auto receiver
	= ctx->get_backend ()->temporary_variable (ctx->peek_fn ().fndecl,
						   NULL_TREE, TREE_TYPE (lhs),
						   lhs, true, expr.get_locus (),
						   &receiver_tmp);
      auto check
	= ctx->get_backend ()->arithmetic_or_logical_expression_checked (
	  op, lhs, rhs, expr.get_locus (), receiver);

      ctx->add_statement (check);
      translated = receiver->get_tree (expr.get_locus ());
    }
  else
    {
      translated = ctx->get_backend ()->arithmetic_or_logical_expression (
	op, lhs, rhs, expr.get_locus ());
    }
}

void
CompileExpr::visit (HIR::CompoundAssignmentExpr &expr)
{
  auto op = expr.get_expr_type ();
  auto lhs = CompileExpr::Compile (expr.get_left_expr ().get (), ctx);
  auto rhs = CompileExpr::Compile (expr.get_right_expr ().get (), ctx);

  // this might be an operator overload situation lets check
  TyTy::FnType *fntype;
  bool is_op_overload = ctx->get_tyctx ()->lookup_operator_overload (
    expr.get_mappings ().get_hirid (), &fntype);
  if (is_op_overload)
    {
      auto lang_item_type
	= Analysis::RustLangItem::CompoundAssignmentOperatorToLangItem (
	  expr.get_expr_type ());
      auto compound_assignment
	= resolve_operator_overload (lang_item_type, expr, lhs, rhs,
				     expr.get_left_expr ().get (),
				     expr.get_right_expr ().get ());
      ctx->add_statement (compound_assignment);

      return;
    }

  if (ctx->in_fn () && !ctx->const_context_p ())
    {
      auto tmp = NULL_TREE;
      auto receiver
	= ctx->get_backend ()->temporary_variable (ctx->peek_fn ().fndecl,
						   NULL_TREE, TREE_TYPE (lhs),
						   lhs, true, expr.get_locus (),
						   &tmp);
      auto check
	= ctx->get_backend ()->arithmetic_or_logical_expression_checked (
	  op, lhs, rhs, expr.get_locus (), receiver);
      ctx->add_statement (check);

      translated = ctx->get_backend ()->assignment_statement (
	lhs, receiver->get_tree (expr.get_locus ()), expr.get_locus ());
    }
  else
    {
      translated = ctx->get_backend ()->arithmetic_or_logical_expression (
	op, lhs, rhs, expr.get_locus ());
    }
}

void
CompileExpr::visit (HIR::NegationExpr &expr)
{
  auto op = expr.get_expr_type ();
  auto negated_expr = CompileExpr::Compile (expr.get_expr ().get (), ctx);
  auto location = expr.get_locus ();

  // this might be an operator overload situation lets check
  TyTy::FnType *fntype;
  bool is_op_overload = ctx->get_tyctx ()->lookup_operator_overload (
    expr.get_mappings ().get_hirid (), &fntype);
  if (is_op_overload)
    {
      auto lang_item_type
	= Analysis::RustLangItem::NegationOperatorToLangItem (op);
      translated
	= resolve_operator_overload (lang_item_type, expr, negated_expr,
				     nullptr, expr.get_expr ().get (), nullptr);
      return;
    }

  translated
    = ctx->get_backend ()->negation_expression (op, negated_expr, location);
}

void
CompileExpr::visit (HIR::ComparisonExpr &expr)
{
  auto op = expr.get_expr_type ();
  auto lhs = CompileExpr::Compile (expr.get_lhs (), ctx);
  auto rhs = CompileExpr::Compile (expr.get_rhs (), ctx);
  auto location = expr.get_locus ();

  translated
    = ctx->get_backend ()->comparison_expression (op, lhs, rhs, location);
}

void
CompileExpr::visit (HIR::LazyBooleanExpr &expr)
{
  auto op = expr.get_expr_type ();
  auto lhs = CompileExpr::Compile (expr.get_lhs (), ctx);
  auto rhs = CompileExpr::Compile (expr.get_rhs (), ctx);
  auto location = expr.get_locus ();

  translated
    = ctx->get_backend ()->lazy_boolean_expression (op, lhs, rhs, location);
}

void
CompileExpr::visit (HIR::TypeCastExpr &expr)
{
  TyTy::BaseType *type_to_cast_to_ty = nullptr;
  if (!ctx->get_tyctx ()->lookup_type (expr.get_mappings ().get_hirid (),
				       &type_to_cast_to_ty))
    {
      translated = error_mark_node;
      return;
    }

  TyTy::BaseType *casted_tyty = nullptr;
  if (!ctx->get_tyctx ()->lookup_type (
	expr.get_casted_expr ()->get_mappings ().get_hirid (), &casted_tyty))
    {
      translated = error_mark_node;
      return;
    }

  auto type_to_cast_to = TyTyResolveCompile::compile (ctx, type_to_cast_to_ty);
  auto casted_expr = CompileExpr::Compile (expr.get_casted_expr ().get (), ctx);

  std::vector<Resolver::Adjustment> *adjustments = nullptr;
  bool ok = ctx->get_tyctx ()->lookup_cast_autoderef_mappings (
    expr.get_mappings ().get_hirid (), &adjustments);
  if (ok)
    {
      casted_expr
	= resolve_adjustements (*adjustments, casted_expr, expr.get_locus ());
    }

  translated
    = type_cast_expression (type_to_cast_to, casted_expr, expr.get_locus ());
}

void
CompileExpr::visit (HIR::IfExpr &expr)
{
  auto stmt = CompileConditionalBlocks::compile (&expr, ctx, nullptr);
  ctx->add_statement (stmt);
}

void
CompileExpr::visit (HIR::IfExprConseqElse &expr)
{
  TyTy::BaseType *if_type = nullptr;
  if (!ctx->get_tyctx ()->lookup_type (expr.get_mappings ().get_hirid (),
				       &if_type))
    {
      rust_error_at (expr.get_locus (),
		     "failed to lookup type of IfExprConseqElse");
      return;
    }

  Bvariable *tmp = NULL;
  bool needs_temp = !if_type->is_unit ();
  if (needs_temp)
    {
      fncontext fnctx = ctx->peek_fn ();
      tree enclosing_scope = ctx->peek_enclosing_scope ();
      tree block_type = TyTyResolveCompile::compile (ctx, if_type);

      bool is_address_taken = false;
      tree ret_var_stmt = nullptr;
      tmp = ctx->get_backend ()->temporary_variable (
	fnctx.fndecl, enclosing_scope, block_type, NULL, is_address_taken,
	expr.get_locus (), &ret_var_stmt);
      ctx->add_statement (ret_var_stmt);
    }

  auto stmt = CompileConditionalBlocks::compile (&expr, ctx, tmp);
  ctx->add_statement (stmt);

  if (tmp != NULL)
    {
      translated = ctx->get_backend ()->var_expression (tmp, expr.get_locus ());
    }
}

void
CompileExpr::visit (HIR::IfExprConseqIf &expr)
{
  TyTy::BaseType *if_type = nullptr;
  if (!ctx->get_tyctx ()->lookup_type (expr.get_mappings ().get_hirid (),
				       &if_type))
    {
      rust_error_at (expr.get_locus (),
		     "failed to lookup type of IfExprConseqElse");
      return;
    }

  Bvariable *tmp = NULL;
  bool needs_temp = !if_type->is_unit ();
  if (needs_temp)
    {
      fncontext fnctx = ctx->peek_fn ();
      tree enclosing_scope = ctx->peek_enclosing_scope ();
      tree block_type = TyTyResolveCompile::compile (ctx, if_type);

      bool is_address_taken = false;
      tree ret_var_stmt = nullptr;
      tmp = ctx->get_backend ()->temporary_variable (
	fnctx.fndecl, enclosing_scope, block_type, NULL, is_address_taken,
	expr.get_locus (), &ret_var_stmt);
      ctx->add_statement (ret_var_stmt);
    }

  auto stmt = CompileConditionalBlocks::compile (&expr, ctx, tmp);
  ctx->add_statement (stmt);

  if (tmp != NULL)
    {
      translated = ctx->get_backend ()->var_expression (tmp, expr.get_locus ());
    }
}

void
CompileExpr::visit (HIR::BlockExpr &expr)
{
  TyTy::BaseType *block_tyty = nullptr;
  if (!ctx->get_tyctx ()->lookup_type (expr.get_mappings ().get_hirid (),
				       &block_tyty))
    {
      rust_error_at (expr.get_locus (), "failed to lookup type of BlockExpr");
      return;
    }

  Bvariable *tmp = NULL;
  bool needs_temp = !block_tyty->is_unit ();
  if (needs_temp)
    {
      fncontext fnctx = ctx->peek_fn ();
      tree enclosing_scope = ctx->peek_enclosing_scope ();
      tree block_type = TyTyResolveCompile::compile (ctx, block_tyty);

      bool is_address_taken = false;
      tree ret_var_stmt = nullptr;
      tmp = ctx->get_backend ()->temporary_variable (
	fnctx.fndecl, enclosing_scope, block_type, NULL, is_address_taken,
	expr.get_locus (), &ret_var_stmt);
      ctx->add_statement (ret_var_stmt);
    }

  auto block_stmt = CompileBlock::compile (&expr, ctx, tmp);
  rust_assert (TREE_CODE (block_stmt) == BIND_EXPR);
  ctx->add_statement (block_stmt);

  if (tmp != NULL)
    {
      translated = ctx->get_backend ()->var_expression (tmp, expr.get_locus ());
    }
}

void
CompileExpr::visit (HIR::UnsafeBlockExpr &expr)
{
  expr.get_block_expr ()->accept_vis (*this);
}

void
CompileExpr::visit (HIR::StructExprStruct &struct_expr)
{
  TyTy::BaseType *tyty = nullptr;
  if (!ctx->get_tyctx ()->lookup_type (struct_expr.get_mappings ().get_hirid (),
				       &tyty))
    {
      rust_error_at (struct_expr.get_locus (), "unknown type");
      return;
    }

  rust_assert (tyty->is_unit ());
  translated = ctx->get_backend ()->unit_expression ();
}

void
CompileExpr::visit (HIR::StructExprStructFields &struct_expr)
{
  TyTy::BaseType *tyty = nullptr;
  if (!ctx->get_tyctx ()->lookup_type (struct_expr.get_mappings ().get_hirid (),
				       &tyty))
    {
      rust_error_at (struct_expr.get_locus (), "unknown type");
      return;
    }

  // it must be an ADT
  rust_assert (tyty->get_kind () == TyTy::TypeKind::ADT);
  TyTy::ADTType *adt = static_cast<TyTy::ADTType *> (tyty);

  // what variant is it?
  int union_disriminator = struct_expr.union_index;
  TyTy::VariantDef *variant = nullptr;
  if (!adt->is_enum ())
    {
      rust_assert (adt->number_of_variants () == 1);
      variant = adt->get_variants ().at (0);
    }
  else
    {
      HirId variant_id;
      bool ok = ctx->get_tyctx ()->lookup_variant_definition (
	struct_expr.get_struct_name ().get_mappings ().get_hirid (),
	&variant_id);
      rust_assert (ok);

      ok
	= adt->lookup_variant_by_id (variant_id, &variant, &union_disriminator);
      rust_assert (ok);
    }

  // compile it
  tree compiled_adt_type = TyTyResolveCompile::compile (ctx, tyty);

  std::vector<tree> arguments;
  if (adt->is_union ())
    {
      rust_assert (struct_expr.get_fields ().size () == 1);

      // assignments are coercion sites so lets convert the rvalue if
      // necessary
      auto respective_field = variant->get_field_at_index (union_disriminator);
      auto expected = respective_field->get_field_type ();

      // process arguments
      auto &argument = struct_expr.get_fields ().at (0);
      auto lvalue_locus
	= ctx->get_mappings ()->lookup_location (expected->get_ty_ref ());
      auto rvalue_locus = argument->get_locus ();
      auto rvalue = CompileStructExprField::Compile (argument.get (), ctx);

      TyTy::BaseType *actual = nullptr;
      bool ok = ctx->get_tyctx ()->lookup_type (
	argument->get_mappings ().get_hirid (), &actual);

      if (ok)
	{
	  rvalue
	    = coercion_site (argument->get_mappings ().get_hirid (), rvalue,
			     actual, expected, lvalue_locus, rvalue_locus);
	}

      // add it to the list
      arguments.push_back (rvalue);
    }
  else
    {
      // this assumes all fields are in order from type resolution and if a
      // base struct was specified those fields are filed via accesors
      for (size_t i = 0; i < struct_expr.get_fields ().size (); i++)
	{
	  // assignments are coercion sites so lets convert the rvalue if
	  // necessary
	  auto respective_field = variant->get_field_at_index (i);
	  auto expected = respective_field->get_field_type ();

	  // process arguments
	  auto &argument = struct_expr.get_fields ().at (i);
	  auto lvalue_locus
	    = ctx->get_mappings ()->lookup_location (expected->get_ty_ref ());
	  auto rvalue_locus = argument->get_locus ();
	  auto rvalue = CompileStructExprField::Compile (argument.get (), ctx);

	  TyTy::BaseType *actual = nullptr;
	  bool ok = ctx->get_tyctx ()->lookup_type (
	    argument->get_mappings ().get_hirid (), &actual);

	  // coerce it if required/possible see
	  // compile/torture/struct_base_init_1.rs
	  if (ok)
	    {
	      rvalue
		= coercion_site (argument->get_mappings ().get_hirid (), rvalue,
				 actual, expected, lvalue_locus, rvalue_locus);
	    }

	  // add it to the list
	  arguments.push_back (rvalue);
	}
    }

  // the constructor depends on whether this is actually an enum or not if
  // its an enum we need to setup the discriminator
  std::vector<tree> ctor_arguments;
  if (adt->is_enum ())
    {
      HIR::Expr *discrim_expr = variant->get_discriminant ();
      tree discrim_expr_node = CompileExpr::Compile (discrim_expr, ctx);
      tree folded_discrim_expr = fold_expr (discrim_expr_node);
      tree qualifier = folded_discrim_expr;

      ctor_arguments.push_back (qualifier);
    }
  for (auto &arg : arguments)
    ctor_arguments.push_back (arg);

  translated = ctx->get_backend ()->constructor_expression (
    compiled_adt_type, adt->is_enum (), ctor_arguments, union_disriminator,
    struct_expr.get_locus ());
}

void
CompileExpr::visit (HIR::GroupedExpr &expr)
{
  translated = CompileExpr::Compile (expr.get_expr_in_parens ().get (), ctx);
}

void
CompileExpr::visit (HIR::FieldAccessExpr &expr)
{
  HIR::Expr *receiver_expr = expr.get_receiver_expr ().get ();
  tree receiver_ref = CompileExpr::Compile (receiver_expr, ctx);

  // resolve the receiver back to ADT type
  TyTy::BaseType *receiver = nullptr;
  if (!ctx->get_tyctx ()->lookup_type (
	expr.get_receiver_expr ()->get_mappings ().get_hirid (), &receiver))
    {
      rust_error_at (expr.get_receiver_expr ()->get_locus (),
		     "unresolved type for receiver");
      return;
    }

  size_t field_index = 0;
  if (receiver->get_kind () == TyTy::TypeKind::ADT)
    {
      TyTy::ADTType *adt = static_cast<TyTy::ADTType *> (receiver);
      rust_assert (!adt->is_enum ());
      rust_assert (adt->number_of_variants () == 1);

      TyTy::VariantDef *variant = adt->get_variants ().at (0);
      bool ok
	= variant->lookup_field (expr.get_field_name (), nullptr, &field_index);
      rust_assert (ok);
    }
  else if (receiver->get_kind () == TyTy::TypeKind::REF)
    {
      TyTy::ReferenceType *r = static_cast<TyTy::ReferenceType *> (receiver);
      TyTy::BaseType *b = r->get_base ();
      rust_assert (b->get_kind () == TyTy::TypeKind::ADT);

      TyTy::ADTType *adt = static_cast<TyTy::ADTType *> (b);
      rust_assert (!adt->is_enum ());
      rust_assert (adt->number_of_variants () == 1);

      TyTy::VariantDef *variant = adt->get_variants ().at (0);
      bool ok
	= variant->lookup_field (expr.get_field_name (), nullptr, &field_index);
      rust_assert (ok);

      tree indirect = indirect_expression (receiver_ref, expr.get_locus ());
      receiver_ref = indirect;
    }

  translated
    = ctx->get_backend ()->struct_field_expression (receiver_ref, field_index,
						    expr.get_locus ());
}

void
CompileExpr::visit (HIR::QualifiedPathInExpression &expr)
{
  translated = ResolvePathRef::Compile (expr, ctx);
}

void
CompileExpr::visit (HIR::PathInExpression &expr)
{
  translated = ResolvePathRef::Compile (expr, ctx);
}

void
CompileExpr::visit (HIR::LoopExpr &expr)
{
  TyTy::BaseType *block_tyty = nullptr;
  if (!ctx->get_tyctx ()->lookup_type (expr.get_mappings ().get_hirid (),
				       &block_tyty))
    {
      rust_error_at (expr.get_locus (), "failed to lookup type of BlockExpr");
      return;
    }

  fncontext fnctx = ctx->peek_fn ();
  tree enclosing_scope = ctx->peek_enclosing_scope ();
  tree block_type = TyTyResolveCompile::compile (ctx, block_tyty);

  bool is_address_taken = false;
  tree ret_var_stmt = NULL_TREE;
  Bvariable *tmp = ctx->get_backend ()->temporary_variable (
    fnctx.fndecl, enclosing_scope, block_type, NULL, is_address_taken,
    expr.get_locus (), &ret_var_stmt);
  ctx->add_statement (ret_var_stmt);
  ctx->push_loop_context (tmp);

  if (expr.has_loop_label ())
    {
      HIR::LoopLabel &loop_label = expr.get_loop_label ();
      tree label
	= ctx->get_backend ()->label (fnctx.fndecl,
				      loop_label.get_lifetime ().get_name (),
				      loop_label.get_locus ());
      tree label_decl = ctx->get_backend ()->label_definition_statement (label);
      ctx->add_statement (label_decl);
      ctx->insert_label_decl (
	loop_label.get_lifetime ().get_mappings ().get_hirid (), label);
    }

  tree loop_begin_label
    = ctx->get_backend ()->label (fnctx.fndecl, "", expr.get_locus ());
  tree loop_begin_label_decl
    = ctx->get_backend ()->label_definition_statement (loop_begin_label);
  ctx->add_statement (loop_begin_label_decl);
  ctx->push_loop_begin_label (loop_begin_label);

  tree code_block
    = CompileBlock::compile (expr.get_loop_block ().get (), ctx, nullptr);
  tree loop_expr
    = ctx->get_backend ()->loop_expression (code_block, expr.get_locus ());
  ctx->add_statement (loop_expr);

  ctx->pop_loop_context ();
  translated = ctx->get_backend ()->var_expression (tmp, expr.get_locus ());

  ctx->pop_loop_begin_label ();
}

void
CompileExpr::visit (HIR::WhileLoopExpr &expr)
{
  fncontext fnctx = ctx->peek_fn ();
  if (expr.has_loop_label ())
    {
      HIR::LoopLabel &loop_label = expr.get_loop_label ();
      tree label
	= ctx->get_backend ()->label (fnctx.fndecl,
				      loop_label.get_lifetime ().get_name (),
				      loop_label.get_locus ());
      tree label_decl = ctx->get_backend ()->label_definition_statement (label);
      ctx->add_statement (label_decl);
      ctx->insert_label_decl (
	loop_label.get_lifetime ().get_mappings ().get_hirid (), label);
    }

  std::vector<Bvariable *> locals;
  Location start_location = expr.get_loop_block ()->get_locus ();
  Location end_location = expr.get_loop_block ()->get_locus (); // FIXME

  tree enclosing_scope = ctx->peek_enclosing_scope ();
  tree loop_block
    = ctx->get_backend ()->block (fnctx.fndecl, enclosing_scope, locals,
				  start_location, end_location);
  ctx->push_block (loop_block);

  tree loop_begin_label
    = ctx->get_backend ()->label (fnctx.fndecl, "", expr.get_locus ());
  tree loop_begin_label_decl
    = ctx->get_backend ()->label_definition_statement (loop_begin_label);
  ctx->add_statement (loop_begin_label_decl);
  ctx->push_loop_begin_label (loop_begin_label);

  tree condition
    = CompileExpr::Compile (expr.get_predicate_expr ().get (), ctx);
  tree exit_condition
    = fold_build1_loc (expr.get_locus ().gcc_location (), TRUTH_NOT_EXPR,
		       boolean_type_node, condition);
  tree exit_expr
    = ctx->get_backend ()->exit_expression (exit_condition, expr.get_locus ());
  ctx->add_statement (exit_expr);

  tree code_block_stmt
    = CompileBlock::compile (expr.get_loop_block ().get (), ctx, nullptr);
  rust_assert (TREE_CODE (code_block_stmt) == BIND_EXPR);
  ctx->add_statement (code_block_stmt);

  ctx->pop_loop_begin_label ();
  ctx->pop_block ();

  tree loop_expr
    = ctx->get_backend ()->loop_expression (loop_block, expr.get_locus ());
  ctx->add_statement (loop_expr);
}

void
CompileExpr::visit (HIR::BreakExpr &expr)
{
  if (expr.has_break_expr ())
    {
      tree compiled_expr = CompileExpr::Compile (expr.get_expr ().get (), ctx);

      Bvariable *loop_result_holder = ctx->peek_loop_context ();
      tree result_reference
	= ctx->get_backend ()->var_expression (loop_result_holder,
					       expr.get_expr ()->get_locus ());

      tree assignment
	= ctx->get_backend ()->assignment_statement (result_reference,
						     compiled_expr,
						     expr.get_locus ());
      ctx->add_statement (assignment);
    }

  if (expr.has_label ())
    {
      NodeId resolved_node_id = UNKNOWN_NODEID;
      if (!ctx->get_resolver ()->lookup_resolved_label (
	    expr.get_label ().get_mappings ().get_nodeid (), &resolved_node_id))
	{
	  rust_error_at (
	    expr.get_label ().get_locus (),
	    "failed to resolve compiled label for label %s",
	    expr.get_label ().get_mappings ().as_string ().c_str ());
	  return;
	}

      HirId ref = UNKNOWN_HIRID;
      if (!ctx->get_mappings ()->lookup_node_to_hir (resolved_node_id, &ref))
	{
	  rust_fatal_error (expr.get_locus (), "reverse lookup label failure");
	  return;
	}

      tree label = NULL_TREE;
      if (!ctx->lookup_label_decl (ref, &label))
	{
	  rust_error_at (expr.get_label ().get_locus (),
			 "failed to lookup compiled label");
	  return;
	}

      tree goto_label
	= ctx->get_backend ()->goto_statement (label, expr.get_locus ());
      ctx->add_statement (goto_label);
    }
  else
    {
      tree exit_expr = ctx->get_backend ()->exit_expression (
	ctx->get_backend ()->boolean_constant_expression (true),
	expr.get_locus ());
      ctx->add_statement (exit_expr);
    }
}

void
CompileExpr::visit (HIR::ContinueExpr &expr)
{
  tree label = ctx->peek_loop_begin_label ();
  if (expr.has_label ())
    {
      NodeId resolved_node_id = UNKNOWN_NODEID;
      if (!ctx->get_resolver ()->lookup_resolved_label (
	    expr.get_label ().get_mappings ().get_nodeid (), &resolved_node_id))
	{
	  rust_error_at (
	    expr.get_label ().get_locus (),
	    "failed to resolve compiled label for label %s",
	    expr.get_label ().get_mappings ().as_string ().c_str ());
	  return;
	}

      HirId ref = UNKNOWN_HIRID;
      if (!ctx->get_mappings ()->lookup_node_to_hir (resolved_node_id, &ref))
	{
	  rust_fatal_error (expr.get_locus (), "reverse lookup label failure");
	  return;
	}

      if (!ctx->lookup_label_decl (ref, &label))
	{
	  rust_error_at (expr.get_label ().get_locus (),
			 "failed to lookup compiled label");
	  return;
	}
    }

  translated = ctx->get_backend ()->goto_statement (label, expr.get_locus ());
}

void
CompileExpr::visit (HIR::BorrowExpr &expr)
{
  tree main_expr = CompileExpr::Compile (expr.get_expr ().get (), ctx);
  if (SLICE_TYPE_P (TREE_TYPE (main_expr)))
    {
      translated = main_expr;
      return;
    }

  TyTy::BaseType *tyty = nullptr;
  if (!ctx->get_tyctx ()->lookup_type (expr.get_mappings ().get_hirid (),
				       &tyty))
    return;

  translated = address_expression (main_expr, expr.get_locus ());
}

void
CompileExpr::visit (HIR::DereferenceExpr &expr)
{
  TyTy::BaseType *tyty = nullptr;
  if (!ctx->get_tyctx ()->lookup_type (expr.get_mappings ().get_hirid (),
				       &tyty))
    {
      rust_fatal_error (expr.get_locus (),
			"did not resolve type for this TupleExpr");
      return;
    }

  tree main_expr = CompileExpr::Compile (expr.get_expr ().get (), ctx);

  // this might be an operator overload situation lets check
  TyTy::FnType *fntype;
  bool is_op_overload = ctx->get_tyctx ()->lookup_operator_overload (
    expr.get_mappings ().get_hirid (), &fntype);
  if (is_op_overload)
    {
      auto lang_item_type = Analysis::RustLangItem::ItemType::DEREF;
      tree operator_overload_call
	= resolve_operator_overload (lang_item_type, expr, main_expr, nullptr,
				     expr.get_expr ().get (), nullptr);

      // rust deref always returns a reference from this overload then we can
      // actually do the indirection
      main_expr = operator_overload_call;
    }

  tree expected_type = TyTyResolveCompile::compile (ctx, tyty);
  if (SLICE_TYPE_P (TREE_TYPE (main_expr)) && SLICE_TYPE_P (expected_type))
    {
      translated = main_expr;
      return;
    }

  translated = indirect_expression (main_expr, expr.get_locus ());
}

void
CompileExpr::visit (HIR::LiteralExpr &expr)
{
  TyTy::BaseType *tyty = nullptr;
  if (!ctx->get_tyctx ()->lookup_type (expr.get_mappings ().get_hirid (),
				       &tyty))
    return;

  switch (expr.get_lit_type ())
    {
    case HIR::Literal::BOOL:
      translated = compile_bool_literal (expr, tyty);
      return;

    case HIR::Literal::INT:
      translated = compile_integer_literal (expr, tyty);
      return;

    case HIR::Literal::FLOAT:
      translated = compile_float_literal (expr, tyty);
      return;

    case HIR::Literal::CHAR:
      translated = compile_char_literal (expr, tyty);
      return;

    case HIR::Literal::BYTE:
      translated = compile_byte_literal (expr, tyty);
      return;

    case HIR::Literal::STRING:
      translated = compile_string_literal (expr, tyty);
      return;

    case HIR::Literal::BYTE_STRING:
      translated = compile_byte_string_literal (expr, tyty);
      return;
    }
}

void
CompileExpr::visit (HIR::AssignmentExpr &expr)
{
  auto lvalue = CompileExpr::Compile (expr.get_lhs (), ctx);
  auto rvalue = CompileExpr::Compile (expr.get_rhs (), ctx);

  // assignments are coercion sites so lets convert the rvalue if necessary
  TyTy::BaseType *expected = nullptr;
  TyTy::BaseType *actual = nullptr;

  bool ok;
  ok = ctx->get_tyctx ()->lookup_type (
    expr.get_lhs ()->get_mappings ().get_hirid (), &expected);
  rust_assert (ok);

  ok = ctx->get_tyctx ()->lookup_type (
    expr.get_rhs ()->get_mappings ().get_hirid (), &actual);
  rust_assert (ok);

  rvalue = coercion_site (expr.get_mappings ().get_hirid (), rvalue, actual,
			  expected, expr.get_lhs ()->get_locus (),
			  expr.get_rhs ()->get_locus ());

  tree assignment
    = ctx->get_backend ()->assignment_statement (lvalue, rvalue,
						 expr.get_locus ());

  ctx->add_statement (assignment);
}

// Helper for sort_tuple_patterns.
// Determine whether Patterns a and b are really the same pattern.
// FIXME: This is a nasty hack to avoid properly implementing a comparison
//        for Patterns, which we really probably do want at some point.
static bool
patterns_mergeable (HIR::Pattern *a, HIR::Pattern *b)
{
  if (!a || !b)
    return false;

  HIR::Pattern::PatternType pat_type = a->get_pattern_type ();
  if (b->get_pattern_type () != pat_type)
    return false;

  switch (pat_type)
    {
      case HIR::Pattern::PatternType::PATH: {
	// FIXME: this is far too naive
	HIR::PathPattern &aref = *static_cast<HIR::PathPattern *> (a);
	HIR::PathPattern &bref = *static_cast<HIR::PathPattern *> (b);
	if (aref.get_num_segments () != bref.get_num_segments ())
	  return false;

	const auto &asegs = aref.get_segments ();
	const auto &bsegs = bref.get_segments ();
	for (size_t i = 0; i < asegs.size (); i++)
	  {
	    if (asegs[i].as_string () != bsegs[i].as_string ())
	      return false;
	  }
	return true;
      }
      break;
      case HIR::Pattern::PatternType::LITERAL: {
	HIR::LiteralPattern &aref = *static_cast<HIR::LiteralPattern *> (a);
	HIR::LiteralPattern &bref = *static_cast<HIR::LiteralPattern *> (b);
	return aref.get_literal ().is_equal (bref.get_literal ());
      }
      break;
      case HIR::Pattern::PatternType::IDENTIFIER: {
	// TODO
      }
      break;
    case HIR::Pattern::PatternType::WILDCARD:
      return true;
      break;

      // TODO

    default:;
    }
  return false;
}

// A little container for rearranging the patterns and cases in a match
// expression while simplifying.
struct PatternMerge
{
  std::unique_ptr<HIR::MatchCase> wildcard;
  std::vector<std::unique_ptr<HIR::Pattern>> heads;
  std::vector<std::vector<HIR::MatchCase>> cases;
};

// Helper for simplify_tuple_match.
// For each tuple pattern in a given match, pull out the first elt of the
// tuple and construct a new MatchCase with the remaining tuple elts as the
// pattern. Return a mapping from each _unique_ first tuple element to a
// vec of cases for a new match.
//
// FIXME: This used to be a std::map<Pattern, Vec<MatchCase>>, but it doesn't
// actually work like we want - the Pattern includes an HIR ID, which is unique
// per Pattern object. This means we don't have a good means for comparing
// Patterns. It would probably be best to actually implement a means of
// properly comparing patterns, and then use an actual map.
//
static struct PatternMerge
sort_tuple_patterns (HIR::MatchExpr &expr)
{
  rust_assert (expr.get_scrutinee_expr ()->get_expression_type ()
	       == HIR::Expr::ExprType::Tuple);

  struct PatternMerge result;
  result.wildcard = nullptr;
  result.heads = std::vector<std::unique_ptr<HIR::Pattern>> ();
  result.cases = std::vector<std::vector<HIR::MatchCase>> ();

  for (auto &match_case : expr.get_match_cases ())
    {
      HIR::MatchArm &case_arm = match_case.get_arm ();

      // FIXME: Note we are only dealing with the first pattern in the arm.
      // The patterns vector in the arm might hold many patterns, which are the
      // patterns separated by the '|' token. Rustc abstracts these as "Or"
      // patterns, and part of its simplification process is to get rid of them.
      // We should get rid of the ORs too, maybe here or earlier than here?
      auto pat = case_arm.get_patterns ()[0]->clone_pattern ();

      // Record wildcards so we can add them in inner matches.
      if (pat->get_pattern_type () == HIR::Pattern::PatternType::WILDCARD)
	{
	  // The *whole* pattern is a wild card (_).
	  result.wildcard
	    = std::unique_ptr<HIR::MatchCase> (new HIR::MatchCase (match_case));
	  continue;
	}

      rust_assert (pat->get_pattern_type ()
		   == HIR::Pattern::PatternType::TUPLE);

      auto ref = *static_cast<HIR::TuplePattern *> (pat.get ());

      rust_assert (ref.has_tuple_pattern_items ());

      auto items
	= HIR::TuplePattern (ref).get_items ()->clone_tuple_pattern_items ();
      if (items->get_pattern_type ()
	  == HIR::TuplePatternItems::TuplePatternItemType::MULTIPLE)
	{
	  auto items_ref
	    = *static_cast<HIR::TuplePatternItemsMultiple *> (items.get ());

	  // Pop the first pattern out
	  auto patterns = std::vector<std::unique_ptr<HIR::Pattern>> ();
	  auto first = items_ref.get_patterns ()[0]->clone_pattern ();
	  for (auto p = items_ref.get_patterns ().begin () + 1;
	       p != items_ref.get_patterns ().end (); p++)
	    {
	      patterns.push_back ((*p)->clone_pattern ());
	    }

	  // if there is only one pattern left, don't make a tuple out of it
	  std::unique_ptr<HIR::Pattern> result_pattern;
	  if (patterns.size () == 1)
	    {
	      result_pattern = std::move (patterns[0]);
	    }
	  else
	    {
	      auto new_items = std::unique_ptr<HIR::TuplePatternItems> (
		new HIR::TuplePatternItemsMultiple (std::move (patterns)));

	      // Construct a TuplePattern from the rest of the patterns
	      result_pattern = std::unique_ptr<HIR::Pattern> (
		new HIR::TuplePattern (ref.get_pattern_mappings (),
				       std::move (new_items),
				       ref.get_locus ()));
	    }

	  // I don't know why we need to make foo separately here but
	  // using the { new_tuple } syntax in new_arm constructor does not
	  // compile.
	  auto foo = std::vector<std::unique_ptr<HIR::Pattern>> ();
	  foo.emplace_back (std::move (result_pattern));
	  HIR::MatchArm new_arm (std::move (foo), Location (), nullptr,
				 AST::AttrVec ());

	  HIR::MatchCase new_case (match_case.get_mappings (), new_arm,
				   match_case.get_expr ()->clone_expr ());

	  bool pushed = false;
	  for (size_t i = 0; i < result.heads.size (); i++)
	    {
	      if (patterns_mergeable (result.heads[i].get (), first.get ()))
		{
		  result.cases[i].push_back (new_case);
		  pushed = true;
		}
	    }

	  if (!pushed)
	    {
	      result.heads.push_back (std::move (first));
	      result.cases.push_back ({new_case});
	    }
	}
      else /* TuplePatternItemType::RANGED */
	{
	  // FIXME
	  gcc_unreachable ();
	}
    }

  return result;
}

// Helper for CompileExpr::visit (HIR::MatchExpr).
// Given a MatchExpr where the scrutinee is some kind of tuple, build an
// equivalent match where only one element of the tuple is examined at a time.
// This resulting match can then be lowered to a SWITCH_EXPR tree directly.
//
// The approach is as follows:
// 1. Split the scrutinee and each pattern into the first (head) and the
//    rest (tail).
// 2. Build a mapping of unique pattern heads to the cases (tail and expr)
//    that shared that pattern head in the original match.
//    (This is the job of sort_tuple_patterns ()).
// 3. For each unique pattern head, build a new MatchCase where the pattern
//    is the unique head, and the expression is a new match where:
//    - The scrutinee is the tail of the original scrutinee
//    - The cases are are those built by the mapping in step 2, i.e. the
//      tails of the patterns and the corresponing expressions from the
//      original match expression.
// 4. Do this recursively for each inner match, until there is nothing more
//    to simplify.
// 5. Build the resulting match which scrutinizes the head of the original
//    scrutinee, using the cases built in step 3.
static HIR::MatchExpr
simplify_tuple_match (HIR::MatchExpr &expr)
{
  if (expr.get_scrutinee_expr ()->get_expression_type ()
      != HIR::Expr::ExprType::Tuple)
    return expr;

  auto ref = *static_cast<HIR::TupleExpr *> (expr.get_scrutinee_expr ().get ());

  auto &tail = ref.get_tuple_elems ();
  rust_assert (tail.size () > 1);

  auto head = std::move (tail[0]);
  tail.erase (tail.begin (), tail.begin () + 1);

  // e.g.
  // match (tupA, tupB, tupC) {
  //   (a1, b1, c1) => { blk1 },
  //   (a2, b2, c2) => { blk2 },
  //   (a1, b3, c3) => { blk3 },
  // }
  // tail = (tupB, tupC)
  // head = tupA

  // Make sure the tail is only a tuple if it consists of at least 2 elements.
  std::unique_ptr<HIR::Expr> remaining;
  if (tail.size () == 1)
    remaining = std::move (tail[0]);
  else
    remaining = std::unique_ptr<HIR::Expr> (
      new HIR::TupleExpr (ref.get_mappings (), std::move (tail),
			  AST::AttrVec (), ref.get_outer_attrs (),
			  ref.get_locus ()));

  // e.g.
  // a1 -> [(b1, c1) => { blk1 },
  //        (b3, c3) => { blk3 }]
  // a2 -> [(b2, c2) => { blk2 }]
  struct PatternMerge map = sort_tuple_patterns (expr);

  std::vector<HIR::MatchCase> cases;
  // Construct the inner match for each unique first elt of the tuple
  // patterns
  for (size_t i = 0; i < map.heads.size (); i++)
    {
      auto inner_match_cases = map.cases[i];

      // If there is a wildcard at the outer match level, then need to
      // propegate the wildcard case into *every* inner match.
      // FIXME: It is probably not correct to add this unconditionally, what if
      // we have a pattern like (a, _, c)? Then there is already a wildcard in
      // the inner matches, and having two will cause two 'default:' blocks
      // which is an error.
      if (map.wildcard != nullptr)
	{
	  inner_match_cases.push_back (*(map.wildcard.get ()));
	}

      // match (tupB, tupC) {
      //   (b1, c1) => { blk1 },
      //   (b3, c3) => { blk3 }
      // }
      HIR::MatchExpr inner_match (expr.get_mappings (),
				  remaining->clone_expr (), inner_match_cases,
				  AST::AttrVec (), expr.get_outer_attrs (),
				  expr.get_locus ());

      inner_match = simplify_tuple_match (inner_match);

      auto outer_arm_pat = std::vector<std::unique_ptr<HIR::Pattern>> ();
      outer_arm_pat.emplace_back (map.heads[i]->clone_pattern ());

      HIR::MatchArm outer_arm (std::move (outer_arm_pat), expr.get_locus ());

      // Need to move the inner match to the heap and put it in a unique_ptr to
      // build the actual match case of the outer expression
      // auto inner_expr = std::unique_ptr<HIR::Expr> (new HIR::MatchExpr
      // (inner_match));
      auto inner_expr = inner_match.clone_expr ();

      // a1 => match (tupB, tupC) { ... }
      HIR::MatchCase outer_case (expr.get_mappings (), outer_arm,
				 std::move (inner_expr));

      cases.push_back (outer_case);
    }

  // If there was a wildcard, make sure to include it at the outer match level
  // too.
  if (map.wildcard != nullptr)
    {
      cases.push_back (*(map.wildcard.get ()));
    }

  // match tupA {
  //   a1 => match (tupB, tupC) {
  //     (b1, c1) => { blk1 },
  //     (b3, c3) => { blk3 }
  //   }
  //   a2 => match (tupB, tupC) {
  //     (b2, c2) => { blk2 }
  //   }
  // }
  HIR::MatchExpr outer_match (expr.get_mappings (), std::move (head), cases,
			      AST::AttrVec (), expr.get_outer_attrs (),
			      expr.get_locus ());

  return outer_match;
}

// Helper for CompileExpr::visit (HIR::MatchExpr).
// Check that the scrutinee of EXPR is a valid kind of expression to match on.
// Return the TypeKind of the scrutinee if it is valid, or TyTy::TypeKind::ERROR
// if not.
static TyTy::TypeKind
check_match_scrutinee (HIR::MatchExpr &expr, Context *ctx)
{
  TyTy::BaseType *scrutinee_expr_tyty = nullptr;
  if (!ctx->get_tyctx ()->lookup_type (
	expr.get_scrutinee_expr ()->get_mappings ().get_hirid (),
	&scrutinee_expr_tyty))
    {
      return TyTy::TypeKind::ERROR;
    }

  TyTy::TypeKind scrutinee_kind = scrutinee_expr_tyty->get_kind ();
  rust_assert ((TyTy::is_primitive_type_kind (scrutinee_kind)
		&& scrutinee_kind != TyTy::TypeKind::NEVER)
	       || scrutinee_kind == TyTy::TypeKind::ADT
	       || scrutinee_kind == TyTy::TypeKind::TUPLE);

  if (scrutinee_kind == TyTy::TypeKind::ADT)
    {
      // this will need to change but for now the first pass implementation,
      // lets assert this is the case
      TyTy::ADTType *adt = static_cast<TyTy::ADTType *> (scrutinee_expr_tyty);
      rust_assert (adt->is_enum ());
      rust_assert (adt->number_of_variants () > 0);
    }
  else if (scrutinee_kind == TyTy::TypeKind::FLOAT)
    {
      // FIXME: CASE_LABEL_EXPR does not support floating point types.
      // Find another way to compile these.
      rust_sorry_at (expr.get_locus (),
		     "match on floating-point types is not yet supported");
    }

  TyTy::BaseType *expr_tyty = nullptr;
  if (!ctx->get_tyctx ()->lookup_type (expr.get_mappings ().get_hirid (),
				       &expr_tyty))
    {
      return TyTy::TypeKind::ERROR;
    }

  return scrutinee_kind;
}

void
CompileExpr::visit (HIR::MatchExpr &expr)
{
  // https://gcc.gnu.org/onlinedocs/gccint/Basic-Statements.html#Basic-Statements
  // TODO
  // SWITCH_ALL_CASES_P is true if the switch includes a default label or the
  // case label ranges cover all possible values of the condition expression

  /* Switch expression.

     TREE_TYPE is the original type of the condition, before any
     language required type conversions.  It may be NULL, in which case
     the original type and final types are assumed to be the same.

     Operand 0 is the expression used to perform the branch,
     Operand 1 is the body of the switch, which probably contains
       CASE_LABEL_EXPRs.  It may also be NULL, in which case operand 2
       must not be NULL.  */
  // DEFTREECODE (SWITCH_EXPR, "switch_expr", tcc_statement, 2)

  /* Used to represent a case label.

     Operand 0 is CASE_LOW.  It may be NULL_TREE, in which case the label
       is a 'default' label.
     Operand 1 is CASE_HIGH.  If it is NULL_TREE, the label is a simple
       (one-value) case label.  If it is non-NULL_TREE, the case is a range.
     Operand 2 is CASE_LABEL, which has the corresponding LABEL_DECL.
     Operand 3 is CASE_CHAIN.  This operand is only used in tree-cfg.cc to
       speed up the lookup of case labels which use a particular edge in
       the control flow graph.  */
  // DEFTREECODE (CASE_LABEL_EXPR, "case_label_expr", tcc_statement, 4)

  TyTy::TypeKind scrutinee_kind = check_match_scrutinee (expr, ctx);
  if (scrutinee_kind == TyTy::TypeKind::ERROR)
    {
      translated = error_mark_node;
      return;
    }

  TyTy::BaseType *expr_tyty = nullptr;
  if (!ctx->get_tyctx ()->lookup_type (expr.get_mappings ().get_hirid (),
				       &expr_tyty))
    {
      translated = error_mark_node;
      return;
    }

  fncontext fnctx = ctx->peek_fn ();
  Bvariable *tmp = NULL;
  bool needs_temp = !expr_tyty->is_unit ();
  if (needs_temp)
    {
      tree enclosing_scope = ctx->peek_enclosing_scope ();
      tree block_type = TyTyResolveCompile::compile (ctx, expr_tyty);

      bool is_address_taken = false;
      tree ret_var_stmt = nullptr;
      tmp = ctx->get_backend ()->temporary_variable (
	fnctx.fndecl, enclosing_scope, block_type, NULL, is_address_taken,
	expr.get_locus (), &ret_var_stmt);
      ctx->add_statement (ret_var_stmt);
    }

  // lets compile the scrutinee expression
  tree match_scrutinee_expr
    = CompileExpr::Compile (expr.get_scrutinee_expr ().get (), ctx);

  tree match_scrutinee_expr_qualifier_expr;
  if (TyTy::is_primitive_type_kind (scrutinee_kind))
    {
      match_scrutinee_expr_qualifier_expr = match_scrutinee_expr;
    }
  else if (scrutinee_kind == TyTy::TypeKind::ADT)
    {
      // need to access qualifier the field, if we use QUAL_UNION_TYPE this
      // would be DECL_QUALIFIER i think. For now this will just access the
      // first record field and its respective qualifier because it will always
      // be set because this is all a big special union
      tree scrutinee_first_record_expr
	= ctx->get_backend ()->struct_field_expression (
	  match_scrutinee_expr, 0, expr.get_scrutinee_expr ()->get_locus ());
      match_scrutinee_expr_qualifier_expr
	= ctx->get_backend ()->struct_field_expression (
	  scrutinee_first_record_expr, 0,
	  expr.get_scrutinee_expr ()->get_locus ());
    }
  else if (scrutinee_kind == TyTy::TypeKind::TUPLE)
    {
      // match on tuple becomes a series of nested switches, with one level
      // for each element of the tuple from left to right.
      auto exprtype = expr.get_scrutinee_expr ()->get_expression_type ();
      switch (exprtype)
	{
	  case HIR::Expr::ExprType::Tuple: {
	    // Build an equivalent expression which is nicer to lower.
	    HIR::MatchExpr outer_match = simplify_tuple_match (expr);

	    // We've rearranged the match into something that lowers better
	    // to GENERIC trees.
	    // For actually doing the lowering we need to compile the match
	    // we've just made. But we're half-way through compiling the
	    // original one.
	    // ...
	    // For now, let's just replace the original with the rearranged one
	    // we just made, and compile that instead. What could go wrong? :)
	    //
	    // FIXME: What about when we decide a temporary is needed above?
	    //        We might have already pushed a statement for it that
	    //        we no longer need. Probably need to rearrange the order
	    //        of these steps.
	    expr = outer_match;

	    scrutinee_kind = check_match_scrutinee (expr, ctx);
	    if (scrutinee_kind == TyTy::TypeKind::ERROR)
	      {
		translated = error_mark_node;
		return;
	      }

	    // Now compile the scrutinee of the simplified match.
	    // FIXME: this part is duplicated from above.
	    match_scrutinee_expr
	      = CompileExpr::Compile (expr.get_scrutinee_expr ().get (), ctx);

	    if (TyTy::is_primitive_type_kind (scrutinee_kind))
	      {
		match_scrutinee_expr_qualifier_expr = match_scrutinee_expr;
	      }
	    else if (scrutinee_kind == TyTy::TypeKind::ADT)
	      {
		// need to access qualifier the field, if we use QUAL_UNION_TYPE
		// this would be DECL_QUALIFIER i think. For now this will just
		// access the first record field and its respective qualifier
		// because it will always be set because this is all a big
		// special union
		tree scrutinee_first_record_expr
		  = ctx->get_backend ()->struct_field_expression (
		    match_scrutinee_expr, 0,
		    expr.get_scrutinee_expr ()->get_locus ());
		match_scrutinee_expr_qualifier_expr
		  = ctx->get_backend ()->struct_field_expression (
		    scrutinee_first_record_expr, 0,
		    expr.get_scrutinee_expr ()->get_locus ());
	      }
	    else
	      {
		// FIXME: There are other cases, but it better not be a Tuple
		gcc_unreachable ();
	      }
	  }
	  break;

	  case HIR::Expr::ExprType::Path: {
	    // FIXME
	    gcc_unreachable ();
	  }
	  break;

	default:
	  gcc_unreachable ();
	}
    }
  else
    {
      // FIXME: match on other types of expressions not yet implemented.
      gcc_unreachable ();
    }

  // setup the end label so the cases can exit properly
  tree fndecl = fnctx.fndecl;
  Location end_label_locus = expr.get_locus (); // FIXME
  tree end_label
    = ctx->get_backend ()->label (fndecl,
				  "" /* empty creates an artificial label */,
				  end_label_locus);
  tree end_label_decl_statement
    = ctx->get_backend ()->label_definition_statement (end_label);

  // setup the switch-body-block
  Location start_location; // FIXME
  Location end_location;   // FIXME
  tree enclosing_scope = ctx->peek_enclosing_scope ();
  tree switch_body_block
    = ctx->get_backend ()->block (fndecl, enclosing_scope, {}, start_location,
				  end_location);
  ctx->push_block (switch_body_block);

  for (auto &kase : expr.get_match_cases ())
    {
      // for now lets just get single pattern's working
      HIR::MatchArm &kase_arm = kase.get_arm ();
      rust_assert (kase_arm.get_patterns ().size () > 0);

      // generate implicit label
      Location arm_locus = kase_arm.get_locus ();
      tree case_label = ctx->get_backend ()->label (
	fndecl, "" /* empty creates an artificial label */, arm_locus);

      // setup the bindings for the block
      for (auto &kase_pattern : kase_arm.get_patterns ())
	{
	  tree switch_kase_expr
	    = CompilePatternCaseLabelExpr::Compile (kase_pattern.get (),
						    case_label, ctx);
	  ctx->add_statement (switch_kase_expr);

	  CompilePatternBindings::Compile (kase_pattern.get (),
					   match_scrutinee_expr, ctx);
	}

      // compile the expr and setup the assignment if required when tmp != NULL
      tree kase_expr_tree = CompileExpr::Compile (kase.get_expr ().get (), ctx);
      if (tmp != NULL)
	{
	  tree result_reference
	    = ctx->get_backend ()->var_expression (tmp, arm_locus);
	  tree assignment
	    = ctx->get_backend ()->assignment_statement (result_reference,
							 kase_expr_tree,
							 arm_locus);
	  ctx->add_statement (assignment);
	}

      // go to end label
      tree goto_end_label = build1_loc (arm_locus.gcc_location (), GOTO_EXPR,
					void_type_node, end_label);
      ctx->add_statement (goto_end_label);
    }

  // setup the switch expression
  tree match_body = ctx->pop_block ();
  tree match_expr_stmt
    = build2_loc (expr.get_locus ().gcc_location (), SWITCH_EXPR,
		  TREE_TYPE (match_scrutinee_expr_qualifier_expr),
		  match_scrutinee_expr_qualifier_expr, match_body);
  ctx->add_statement (match_expr_stmt);
  ctx->add_statement (end_label_decl_statement);

  if (tmp != NULL)
    {
      translated = ctx->get_backend ()->var_expression (tmp, expr.get_locus ());
    }
}

void
CompileExpr::visit (HIR::CallExpr &expr)
{
  TyTy::BaseType *tyty = nullptr;
  if (!ctx->get_tyctx ()->lookup_type (
	expr.get_fnexpr ()->get_mappings ().get_hirid (), &tyty))
    {
      rust_error_at (expr.get_locus (), "unknown type");
      return;
    }

  // must be a tuple constructor
  bool is_adt_ctor = tyty->get_kind () == TyTy::TypeKind::ADT;
  if (is_adt_ctor)
    {
      rust_assert (tyty->get_kind () == TyTy::TypeKind::ADT);
      TyTy::ADTType *adt = static_cast<TyTy::ADTType *> (tyty);
      tree compiled_adt_type = TyTyResolveCompile::compile (ctx, tyty);

      // what variant is it?
      int union_disriminator = -1;
      TyTy::VariantDef *variant = nullptr;
      if (!adt->is_enum ())
	{
	  rust_assert (adt->number_of_variants () == 1);
	  variant = adt->get_variants ().at (0);
	}
      else
	{
	  HirId variant_id;
	  bool ok = ctx->get_tyctx ()->lookup_variant_definition (
	    expr.get_fnexpr ()->get_mappings ().get_hirid (), &variant_id);
	  rust_assert (ok);

	  ok = adt->lookup_variant_by_id (variant_id, &variant,
					  &union_disriminator);
	  rust_assert (ok);
	}

      // this assumes all fields are in order from type resolution and if a
      // base struct was specified those fields are filed via accesors
      std::vector<tree> arguments;
      for (size_t i = 0; i < expr.get_arguments ().size (); i++)
	{
	  auto &argument = expr.get_arguments ().at (i);
	  auto rvalue = CompileExpr::Compile (argument.get (), ctx);

	  // assignments are coercion sites so lets convert the rvalue if
	  // necessary
	  auto respective_field = variant->get_field_at_index (i);
	  auto expected = respective_field->get_field_type ();

	  TyTy::BaseType *actual = nullptr;
	  bool ok = ctx->get_tyctx ()->lookup_type (
	    argument->get_mappings ().get_hirid (), &actual);
	  rust_assert (ok);

	  // coerce it if required
	  Location lvalue_locus
	    = ctx->get_mappings ()->lookup_location (expected->get_ty_ref ());
	  Location rvalue_locus = argument->get_locus ();
	  rvalue
	    = coercion_site (argument->get_mappings ().get_hirid (), rvalue,
			     actual, expected, lvalue_locus, rvalue_locus);

	  // add it to the list
	  arguments.push_back (rvalue);
	}

      // the constructor depends on whether this is actually an enum or not if
      // its an enum we need to setup the discriminator
      std::vector<tree> ctor_arguments;
      if (adt->is_enum ())
	{
	  HIR::Expr *discrim_expr = variant->get_discriminant ();
	  tree discrim_expr_node = CompileExpr::Compile (discrim_expr, ctx);
	  tree folded_discrim_expr = fold_expr (discrim_expr_node);
	  tree qualifier = folded_discrim_expr;

	  ctor_arguments.push_back (qualifier);
	}
      for (auto &arg : arguments)
	ctor_arguments.push_back (arg);

      translated = ctx->get_backend ()->constructor_expression (
	compiled_adt_type, adt->is_enum (), ctor_arguments, union_disriminator,
	expr.get_locus ());

      return;
    }

  auto get_parameter_tyty_at_index
    = [] (const TyTy::BaseType *base, size_t index,
	  TyTy::BaseType **result) -> bool {
    bool is_fn = base->get_kind () == TyTy::TypeKind::FNDEF
		 || base->get_kind () == TyTy::TypeKind::FNPTR;
    rust_assert (is_fn);

    if (base->get_kind () == TyTy::TypeKind::FNPTR)
      {
	const TyTy::FnPtr *fn = static_cast<const TyTy::FnPtr *> (base);
	*result = fn->param_at (index);

	return true;
      }

    const TyTy::FnType *fn = static_cast<const TyTy::FnType *> (base);
    auto param = fn->param_at (index);
    *result = param.second;

    return true;
  };

  auto fn_address = CompileExpr::Compile (expr.get_fnexpr (), ctx);

  // is this a closure call?
  bool possible_trait_call
    = generate_possible_fn_trait_call (expr, fn_address, &translated);
  if (possible_trait_call)
    return;

  bool is_varadic = false;
  if (tyty->get_kind () == TyTy::TypeKind::FNDEF)
    {
      const TyTy::FnType *fn = static_cast<const TyTy::FnType *> (tyty);
      is_varadic = fn->is_varadic ();
    }

  size_t required_num_args = expr.get_arguments ().size ();
  if (tyty->get_kind () == TyTy::TypeKind::FNDEF)
    {
      const TyTy::FnType *fn = static_cast<const TyTy::FnType *> (tyty);
      required_num_args = fn->num_params ();
    }
  else if (tyty->get_kind () == TyTy::TypeKind::FNPTR)
    {
      const TyTy::FnPtr *fn = static_cast<const TyTy::FnPtr *> (tyty);
      required_num_args = fn->num_params ();
    }

  std::vector<tree> args;
  for (size_t i = 0; i < expr.get_arguments ().size (); i++)
    {
      auto &argument = expr.get_arguments ().at (i);
      auto rvalue = CompileExpr::Compile (argument.get (), ctx);

      if (is_varadic && i >= required_num_args)
	{
	  args.push_back (rvalue);
	  continue;
	}

      // assignments are coercion sites so lets convert the rvalue if
      // necessary
      bool ok;
      TyTy::BaseType *expected = nullptr;
      ok = get_parameter_tyty_at_index (tyty, i, &expected);
      rust_assert (ok);

      TyTy::BaseType *actual = nullptr;
      ok = ctx->get_tyctx ()->lookup_type (
	argument->get_mappings ().get_hirid (), &actual);
      rust_assert (ok);

      // coerce it if required
      Location lvalue_locus
	= ctx->get_mappings ()->lookup_location (expected->get_ty_ref ());
      Location rvalue_locus = argument->get_locus ();
      rvalue = coercion_site (argument->get_mappings ().get_hirid (), rvalue,
			      actual, expected, lvalue_locus, rvalue_locus);

      // add it to the list
      args.push_back (rvalue);
    }

  // must be a regular call to a function
  translated = ctx->get_backend ()->call_expression (fn_address, args, nullptr,
						     expr.get_locus ());
}

void
CompileExpr::visit (HIR::MethodCallExpr &expr)
{
  // method receiver
  tree self = CompileExpr::Compile (expr.get_receiver ().get (), ctx);

  // lookup the resolved name
  NodeId resolved_node_id = UNKNOWN_NODEID;
  if (!ctx->get_resolver ()->lookup_resolved_name (
	expr.get_mappings ().get_nodeid (), &resolved_node_id))
    {
      rust_error_at (expr.get_locus (), "failed to lookup resolved MethodCall");
      return;
    }

  // reverse lookup
  HirId ref;
  if (!ctx->get_mappings ()->lookup_node_to_hir (resolved_node_id, &ref))
    {
      rust_fatal_error (expr.get_locus (), "reverse lookup failure");
      return;
    }

  // lookup the expected function type
  TyTy::BaseType *lookup_fntype = nullptr;
  bool ok = ctx->get_tyctx ()->lookup_type (
    expr.get_method_name ().get_mappings ().get_hirid (), &lookup_fntype);
  rust_assert (ok);
  rust_assert (lookup_fntype->get_kind () == TyTy::TypeKind::FNDEF);
  TyTy::FnType *fntype = static_cast<TyTy::FnType *> (lookup_fntype);

  TyTy::BaseType *receiver = nullptr;
  ok = ctx->get_tyctx ()->lookup_receiver (expr.get_mappings ().get_hirid (),
					   &receiver);
  rust_assert (ok);

  bool is_dyn_dispatch
    = receiver->get_root ()->get_kind () == TyTy::TypeKind::DYNAMIC;
  bool is_generic_receiver = receiver->get_kind () == TyTy::TypeKind::PARAM;
  if (is_generic_receiver)
    {
      TyTy::ParamType *p = static_cast<TyTy::ParamType *> (receiver);
      receiver = p->resolve ();
    }

  tree fn_expr = error_mark_node;
  if (is_dyn_dispatch)
    {
      const TyTy::DynamicObjectType *dyn
	= static_cast<const TyTy::DynamicObjectType *> (receiver->get_root ());

      std::vector<HIR::Expr *> arguments;
      for (auto &arg : expr.get_arguments ())
	arguments.push_back (arg.get ());

      fn_expr
	= get_fn_addr_from_dyn (dyn, receiver, fntype, self, expr.get_locus ());
      self = get_receiver_from_dyn (dyn, receiver, fntype, self,
				    expr.get_locus ());
    }
  else
    {
      // lookup compiled functions since it may have already been compiled
      HIR::PathExprSegment method_name = expr.get_method_name ();
      HIR::PathIdentSegment segment_name = method_name.get_segment ();
      fn_expr
	= resolve_method_address (fntype, ref, receiver, segment_name,
				  expr.get_mappings (), expr.get_locus ());
    }

  // lookup the autoderef mappings
  HirId autoderef_mappings_id
    = expr.get_receiver ()->get_mappings ().get_hirid ();
  std::vector<Resolver::Adjustment> *adjustments = nullptr;
  ok = ctx->get_tyctx ()->lookup_autoderef_mappings (autoderef_mappings_id,
						     &adjustments);
  rust_assert (ok);

  // apply adjustments for the fn call
  self = resolve_adjustements (*adjustments, self,
			       expr.get_receiver ()->get_locus ());

  std::vector<tree> args;
  args.push_back (self); // adjusted self

  // normal args
  for (size_t i = 0; i < expr.get_arguments ().size (); i++)
    {
      auto &argument = expr.get_arguments ().at (i);
      auto rvalue = CompileExpr::Compile (argument.get (), ctx);

      // assignments are coercion sites so lets convert the rvalue if
      // necessary, offset from the already adjusted implicit self
      bool ok;
      TyTy::BaseType *expected = fntype->param_at (i + 1).second;

      TyTy::BaseType *actual = nullptr;
      ok = ctx->get_tyctx ()->lookup_type (
	argument->get_mappings ().get_hirid (), &actual);
      rust_assert (ok);

      // coerce it if required
      Location lvalue_locus
	= ctx->get_mappings ()->lookup_location (expected->get_ty_ref ());
      Location rvalue_locus = argument->get_locus ();
      rvalue = coercion_site (argument->get_mappings ().get_hirid (), rvalue,
			      actual, expected, lvalue_locus, rvalue_locus);

      // add it to the list
      args.push_back (rvalue);
    }

  translated = ctx->get_backend ()->call_expression (fn_expr, args, nullptr,
						     expr.get_locus ());
}

tree
CompileExpr::get_fn_addr_from_dyn (const TyTy::DynamicObjectType *dyn,
				   TyTy::BaseType *receiver,
				   TyTy::FnType *fntype, tree receiver_ref,
				   Location expr_locus)
{
  size_t offs = 0;
  const Resolver::TraitItemReference *ref = nullptr;
  for (auto &bound : dyn->get_object_items ())
    {
      const Resolver::TraitItemReference *item = bound.first;
      auto t = item->get_tyty ();
      rust_assert (t->get_kind () == TyTy::TypeKind::FNDEF);
      auto ft = static_cast<TyTy::FnType *> (t);

      if (ft->get_id () == fntype->get_id ())
	{
	  ref = item;
	  break;
	}
      offs++;
    }

  if (ref == nullptr)
    return error_mark_node;

  // get any indirection sorted out
  if (receiver->get_kind () == TyTy::TypeKind::REF)
    {
      tree indirect = indirect_expression (receiver_ref, expr_locus);
      receiver_ref = indirect;
    }

  // cast it to the correct fntype
  tree expected_fntype = TyTyResolveCompile::compile (ctx, fntype, true);
  tree idx = build_int_cst (size_type_node, offs);

  tree vtable_ptr
    = ctx->get_backend ()->struct_field_expression (receiver_ref, 1,
						    expr_locus);
  tree vtable_array_access = build4_loc (expr_locus.gcc_location (), ARRAY_REF,
					 TREE_TYPE (TREE_TYPE (vtable_ptr)),
					 vtable_ptr, idx, NULL_TREE, NULL_TREE);

  tree vcall
    = build3_loc (expr_locus.gcc_location (), OBJ_TYPE_REF, expected_fntype,
		  vtable_array_access, receiver_ref, idx);

  return vcall;
}

tree
CompileExpr::get_receiver_from_dyn (const TyTy::DynamicObjectType *dyn,
				    TyTy::BaseType *receiver,
				    TyTy::FnType *fntype, tree receiver_ref,
				    Location expr_locus)
{
  // get any indirection sorted out
  if (receiver->get_kind () == TyTy::TypeKind::REF)
    {
      tree indirect = indirect_expression (receiver_ref, expr_locus);
      receiver_ref = indirect;
    }

  // access the offs + 1 for the fnptr and offs=0 for the reciever obj
  return ctx->get_backend ()->struct_field_expression (receiver_ref, 0,
						       expr_locus);
}

tree
CompileExpr::resolve_method_address (TyTy::FnType *fntype, HirId ref,
				     TyTy::BaseType *receiver,
				     HIR::PathIdentSegment &segment,
				     Analysis::NodeMapping expr_mappings,
				     Location expr_locus)
{
  // lookup compiled functions since it may have already been compiled
  tree fn = NULL_TREE;
  if (ctx->lookup_function_decl (fntype->get_ty_ref (), &fn))
    {
      return address_expression (fn, expr_locus);
    }

  // Now we can try and resolve the address since this might be a forward
  // declared function, generic function which has not be compiled yet or
  // its an not yet trait bound function
  HIR::ImplItem *resolved_item
    = ctx->get_mappings ()->lookup_hir_implitem (ref, nullptr);
  if (resolved_item != nullptr)
    {
      if (!fntype->has_subsititions_defined ())
	return CompileInherentImplItem::Compile (resolved_item, ctx);

      return CompileInherentImplItem::Compile (resolved_item, ctx, fntype);
    }

  // it might be resolved to a trait item
  HIR::TraitItem *trait_item
    = ctx->get_mappings ()->lookup_hir_trait_item (ref);
  HIR::Trait *trait = ctx->get_mappings ()->lookup_trait_item_mapping (
    trait_item->get_mappings ().get_hirid ());

  Resolver::TraitReference *trait_ref
    = &Resolver::TraitReference::error_node ();
  bool ok = ctx->get_tyctx ()->lookup_trait_reference (
    trait->get_mappings ().get_defid (), &trait_ref);
  rust_assert (ok);

  // the type resolver can only resolve type bounds to their trait
  // item so its up to us to figure out if this path should resolve
  // to an trait-impl-block-item or if it can be defaulted to the
  // trait-impl-item's definition

  auto root = receiver->get_root ();
  auto candidates
    = Resolver::PathProbeType::Probe (root, segment, true /* probe_impls */,
				      false /* probe_bounds */,
				      true /* ignore_mandatory_trait_items */);
  if (candidates.size () == 0)
    {
      // this means we are defaulting back to the trait_item if
      // possible
      Resolver::TraitItemReference *trait_item_ref = nullptr;
      bool ok = trait_ref->lookup_hir_trait_item (*trait_item, &trait_item_ref);
      rust_assert (ok);				    // found
      rust_assert (trait_item_ref->is_optional ()); // has definition

      // FIXME Optional means it has a definition and an associated
      // block which can be a default implementation, if it does not
      // contain an implementation we should actually return
      // error_mark_node

      return CompileTraitItem::Compile (trait_item_ref->get_hir_trait_item (),
					ctx, fntype, true, expr_locus);
    }
  else
    {
      // FIXME this will be a case to return error_mark_node, there is
      // an error scenario where a Trait Foo has a method Bar, but this
      // receiver does not implement this trait or has an incompatible
      // implementation and we should just return error_mark_node

      rust_assert (candidates.size () == 1);
      auto &candidate = *candidates.begin ();
      rust_assert (candidate.is_impl_candidate ());
      rust_assert (candidate.ty->get_kind () == TyTy::TypeKind::FNDEF);
      TyTy::FnType *candidate_call = static_cast<TyTy::FnType *> (candidate.ty);

      HIR::ImplItem *impl_item = candidate.item.impl.impl_item;
      if (!candidate_call->has_subsititions_defined ())
	return CompileInherentImplItem::Compile (impl_item, ctx);

      TyTy::BaseType *monomorphized = candidate_call;
      if (candidate_call->needs_generic_substitutions ())
	{
	  TyTy::BaseType *infer_impl_call
	    = candidate_call->infer_substitions (expr_locus);
	  monomorphized = infer_impl_call->unify (fntype);
	}

      return CompileInherentImplItem::Compile (impl_item, ctx, monomorphized);
    }
}

tree
CompileExpr::resolve_operator_overload (
  Analysis::RustLangItem::ItemType lang_item_type, HIR::OperatorExprMeta expr,
  tree lhs, tree rhs, HIR::Expr *lhs_expr, HIR::Expr *rhs_expr)
{
  TyTy::FnType *fntype;
  bool is_op_overload = ctx->get_tyctx ()->lookup_operator_overload (
    expr.get_mappings ().get_hirid (), &fntype);
  rust_assert (is_op_overload);

  // lookup the resolved name
  NodeId resolved_node_id = UNKNOWN_NODEID;
  bool ok = ctx->get_resolver ()->lookup_resolved_name (
    expr.get_mappings ().get_nodeid (), &resolved_node_id);
  rust_assert (ok);

  // reverse lookup
  HirId ref;
  ok = ctx->get_mappings ()->lookup_node_to_hir (resolved_node_id, &ref);
  rust_assert (ok);

  TyTy::BaseType *receiver = nullptr;
  ok = ctx->get_tyctx ()->lookup_receiver (expr.get_mappings ().get_hirid (),
					   &receiver);
  rust_assert (ok);

  bool is_generic_receiver = receiver->get_kind () == TyTy::TypeKind::PARAM;
  if (is_generic_receiver)
    {
      TyTy::ParamType *p = static_cast<TyTy::ParamType *> (receiver);
      receiver = p->resolve ();
    }

  // lookup compiled functions since it may have already been compiled
  HIR::PathIdentSegment segment_name (
    Analysis::RustLangItem::ToString (lang_item_type));
  tree fn_expr
    = resolve_method_address (fntype, ref, receiver, segment_name,
			      expr.get_mappings (), expr.get_locus ());

  // lookup the autoderef mappings
  std::vector<Resolver::Adjustment> *adjustments = nullptr;
  ok = ctx->get_tyctx ()->lookup_autoderef_mappings (
    expr.get_lvalue_mappings ().get_hirid (), &adjustments);
  rust_assert (ok);

  // apply adjustments for the fn call
  tree self = resolve_adjustements (*adjustments, lhs, lhs_expr->get_locus ());

  std::vector<tree> args;
  args.push_back (self); // adjusted self
  if (rhs != nullptr)	 // can be null for negation_expr (unary ones)
    args.push_back (rhs);

  return ctx->get_backend ()->call_expression (fn_expr, args, nullptr,
					       expr.get_locus ());
}

tree
CompileExpr::compile_bool_literal (const HIR::LiteralExpr &expr,
				   const TyTy::BaseType *tyty)
{
  rust_assert (expr.get_lit_type () == HIR::Literal::BOOL);

  const auto literal_value = expr.get_literal ();
  bool bval = literal_value.as_string ().compare ("true") == 0;
  return ctx->get_backend ()->boolean_constant_expression (bval);
}

tree
CompileExpr::compile_integer_literal (const HIR::LiteralExpr &expr,
				      const TyTy::BaseType *tyty)
{
  rust_assert (expr.get_lit_type () == HIR::Literal::INT);
  const auto literal_value = expr.get_literal ();

  tree type = TyTyResolveCompile::compile (ctx, tyty);

  mpz_t ival;
  if (mpz_init_set_str (ival, literal_value.as_string ().c_str (), 10) != 0)
    {
      rust_error_at (expr.get_locus (), "bad number in literal");
      return error_mark_node;
    }

  mpz_t type_min;
  mpz_t type_max;
  mpz_init (type_min);
  mpz_init (type_max);
  get_type_static_bounds (type, type_min, type_max);

  if (mpz_cmp (ival, type_min) < 0 || mpz_cmp (ival, type_max) > 0)
    {
      rust_error_at (expr.get_locus (),
		     "integer overflows the respective type %<%s%>",
		     tyty->get_name ().c_str ());
      return error_mark_node;
    }

  tree result = wide_int_to_tree (type, wi::from_mpz (type, ival, true));

  mpz_clear (type_min);
  mpz_clear (type_max);
  mpz_clear (ival);

  return result;
}

tree
CompileExpr::compile_float_literal (const HIR::LiteralExpr &expr,
				    const TyTy::BaseType *tyty)
{
  rust_assert (expr.get_lit_type () == HIR::Literal::FLOAT);
  const auto literal_value = expr.get_literal ();

  mpfr_t fval;
  if (mpfr_init_set_str (fval, literal_value.as_string ().c_str (), 10,
			 MPFR_RNDN)
      != 0)
    {
      rust_error_at (expr.get_locus (), "bad number in literal");
      return error_mark_node;
    }

  tree type = TyTyResolveCompile::compile (ctx, tyty);

  // taken from:
  // see go/gofrontend/expressions.cc:check_float_type
  mpfr_exp_t exp = mpfr_get_exp (fval);
  bool real_value_overflow = exp > TYPE_PRECISION (type);

  REAL_VALUE_TYPE r1;
  real_from_mpfr (&r1, fval, type, GMP_RNDN);
  REAL_VALUE_TYPE r2;
  real_convert (&r2, TYPE_MODE (type), &r1);

  tree real_value = build_real (type, r2);
  if (TREE_OVERFLOW (real_value) || real_value_overflow)
    {
      rust_error_at (expr.get_locus (),
		     "decimal overflows the respective type %<%s%>",
		     tyty->get_name ().c_str ());
      return error_mark_node;
    }

  return real_value;
}

tree
CompileExpr::compile_char_literal (const HIR::LiteralExpr &expr,
				   const TyTy::BaseType *tyty)
{
  rust_assert (expr.get_lit_type () == HIR::Literal::CHAR);
  const auto literal_value = expr.get_literal ();

  // FIXME needs wchar_t
  char c = literal_value.as_string ().c_str ()[0];
  return ctx->get_backend ()->wchar_constant_expression (c);
}

tree
CompileExpr::compile_byte_literal (const HIR::LiteralExpr &expr,
				   const TyTy::BaseType *tyty)
{
  rust_assert (expr.get_lit_type () == HIR::Literal::BYTE);
  const auto literal_value = expr.get_literal ();

  tree type = TyTyResolveCompile::compile (ctx, tyty);
  char c = literal_value.as_string ().c_str ()[0];
  return build_int_cst (type, c);
}

tree
CompileExpr::compile_string_literal (const HIR::LiteralExpr &expr,
				     const TyTy::BaseType *tyty)
{
  tree fat_pointer = TyTyResolveCompile::compile (ctx, tyty);

  rust_assert (expr.get_lit_type () == HIR::Literal::STRING);
  const auto literal_value = expr.get_literal ();

  auto base = ctx->get_backend ()->string_constant_expression (
    literal_value.as_string ());
  tree data = address_expression (base, expr.get_locus ());

  TyTy::BaseType *usize = nullptr;
  bool ok = ctx->get_tyctx ()->lookup_builtin ("usize", &usize);
  rust_assert (ok);
  tree type = TyTyResolveCompile::compile (ctx, usize);

  tree size = build_int_cstu (type, literal_value.as_string ().size ());

  return ctx->get_backend ()->constructor_expression (fat_pointer, false,
						      {data, size}, -1,
						      expr.get_locus ());
}

tree
CompileExpr::compile_byte_string_literal (const HIR::LiteralExpr &expr,
					  const TyTy::BaseType *tyty)
{
  rust_assert (expr.get_lit_type () == HIR::Literal::BYTE_STRING);

  // the type here is &[ty; capacity]
  rust_assert (tyty->get_kind () == TyTy::TypeKind::REF);
  const auto ref_tyty = static_cast<const TyTy::ReferenceType *> (tyty);
  auto base_tyty = ref_tyty->get_base ();
  rust_assert (base_tyty->get_kind () == TyTy::TypeKind::ARRAY);
  auto array_tyty = static_cast<TyTy::ArrayType *> (base_tyty);

  std::string value_str = expr.get_literal ().as_string ();
  std::vector<tree> vals;
  std::vector<unsigned long> indexes;
  for (size_t i = 0; i < value_str.size (); i++)
    {
      char b = value_str.at (i);
      tree bb = ctx->get_backend ()->char_constant_expression (b);
      vals.push_back (bb);
      indexes.push_back (i);
    }

  tree array_type = TyTyResolveCompile::compile (ctx, array_tyty);
  tree constructed
    = ctx->get_backend ()->array_constructor_expression (array_type, indexes,
							 vals,
							 expr.get_locus ());

  return address_expression (constructed, expr.get_locus ());
}

tree
CompileExpr::type_cast_expression (tree type_to_cast_to, tree expr_tree,
				   Location location)
{
  if (type_to_cast_to == error_mark_node || expr_tree == error_mark_node
      || TREE_TYPE (expr_tree) == error_mark_node)
    return error_mark_node;

  if (ctx->get_backend ()->type_size (type_to_cast_to) == 0
      || TREE_TYPE (expr_tree) == void_type_node)
    {
      // Do not convert zero-sized types.
      return expr_tree;
    }
  else if (TREE_CODE (type_to_cast_to) == INTEGER_TYPE)
    {
      tree cast = convert_to_integer (type_to_cast_to, expr_tree);
      // FIXME check for TREE_OVERFLOW?
      return cast;
    }
  else if (TREE_CODE (type_to_cast_to) == REAL_TYPE)
    {
      tree cast = convert_to_real (type_to_cast_to, expr_tree);
      // FIXME
      // We might need to check that the tree is MAX val and thusly saturate it
      // to inf. we can get the bounds and check the value if its >= or <= to
      // the min and max bounds
      //
      // https://github.com/Rust-GCC/gccrs/issues/635
      return cast;
    }
  else if (TREE_CODE (type_to_cast_to) == COMPLEX_TYPE)
    {
      return convert_to_complex (type_to_cast_to, expr_tree);
    }
  else if (TREE_CODE (type_to_cast_to) == POINTER_TYPE
	   && TREE_CODE (TREE_TYPE (expr_tree)) == INTEGER_TYPE)
    {
      return convert_to_pointer (type_to_cast_to, expr_tree);
    }
  else if (TREE_CODE (type_to_cast_to) == RECORD_TYPE
	   || TREE_CODE (type_to_cast_to) == ARRAY_TYPE)
    {
      return fold_build1_loc (location.gcc_location (), VIEW_CONVERT_EXPR,
			      type_to_cast_to, expr_tree);
    }
  else if (TREE_CODE (type_to_cast_to) == POINTER_TYPE
	   && SLICE_TYPE_P (TREE_TYPE (expr_tree)))
    {
      // returning a raw cast using NOP_EXPR seems to resut in an ICE:
      //
      // Analyzing compilation unit
      // Performing interprocedural optimizations
      //  <*free_lang_data> {heap 2644k} <visibility> {heap 2644k}
      //  <build_ssa_passes> {heap 2644k} <opt_local_passes> {heap 2644k}during
      //  GIMPLE pass: cddce
      // In function ‘*T::as_ptr<i32>’:
      // rust1: internal compiler error: in propagate_necessity, at
      // tree-ssa-dce.cc:984 0x1d5b43e propagate_necessity
      //         ../../gccrs/gcc/tree-ssa-dce.cc:984
      // 0x1d5e180 perform_tree_ssa_dce
      //         ../../gccrs/gcc/tree-ssa-dce.cc:1876
      // 0x1d5e2c8 tree_ssa_cd_dce
      //         ../../gccrs/gcc/tree-ssa-dce.cc:1920
      // 0x1d5e49a execute
      //         ../../gccrs/gcc/tree-ssa-dce.cc:1992

      // this is returning the direct raw pointer of the slice an assumes a very
      // specific layout
      return ctx->get_backend ()->struct_field_expression (expr_tree, 0,
							   location);
    }

  return fold_convert_loc (location.gcc_location (), type_to_cast_to,
			   expr_tree);
}

void
CompileExpr::visit (HIR::ArrayExpr &expr)
{
  TyTy::BaseType *tyty = nullptr;
  if (!ctx->get_tyctx ()->lookup_type (expr.get_mappings ().get_hirid (),
				       &tyty))
    {
      rust_fatal_error (expr.get_locus (),
			"did not resolve type for this array expr");
      return;
    }

  tree array_type = TyTyResolveCompile::compile (ctx, tyty);
  if (TREE_CODE (array_type) != ARRAY_TYPE)
    {
      translated = error_mark_node;
      return;
    }

  rust_assert (tyty->get_kind () == TyTy::TypeKind::ARRAY);
  const TyTy::ArrayType &array_tyty
    = static_cast<const TyTy::ArrayType &> (*tyty);

  HIR::ArrayElems &elements = *expr.get_internal_elements ();
  switch (elements.get_array_expr_type ())
    {
      case HIR::ArrayElems::ArrayExprType::VALUES: {
	HIR::ArrayElemsValues &elems
	  = static_cast<HIR::ArrayElemsValues &> (elements);
	translated
	  = array_value_expr (expr.get_locus (), array_tyty, array_type, elems);
      }
      return;

    case HIR::ArrayElems::ArrayExprType::COPIED:
      HIR::ArrayElemsCopied &elems
	= static_cast<HIR::ArrayElemsCopied &> (elements);
      translated
	= array_copied_expr (expr.get_locus (), array_tyty, array_type, elems);
    }
}

tree
CompileExpr::array_value_expr (Location expr_locus,
			       const TyTy::ArrayType &array_tyty,
			       tree array_type, HIR::ArrayElemsValues &elems)
{
  std::vector<unsigned long> indexes;
  std::vector<tree> constructor;
  size_t i = 0;
  for (auto &elem : elems.get_values ())
    {
      tree translated_expr = CompileExpr::Compile (elem.get (), ctx);
      constructor.push_back (translated_expr);
      indexes.push_back (i++);
    }

  return ctx->get_backend ()->array_constructor_expression (array_type, indexes,
							    constructor,
							    expr_locus);
}

tree
CompileExpr::array_copied_expr (Location expr_locus,
				const TyTy::ArrayType &array_tyty,
				tree array_type, HIR::ArrayElemsCopied &elems)
{
  //  see gcc/cp/typeck2.cc:1369-1401
  gcc_assert (TREE_CODE (array_type) == ARRAY_TYPE);
  tree domain = TYPE_DOMAIN (array_type);
  if (!domain)
    return error_mark_node;

  if (!TREE_CONSTANT (TYPE_MAX_VALUE (domain)))
    {
      rust_error_at (expr_locus, "non const capacity domain %qT", array_type);
      return error_mark_node;
    }

  ctx->push_const_context ();
  tree capacity_expr = CompileExpr::Compile (elems.get_num_copies_expr (), ctx);
  ctx->pop_const_context ();

  if (!TREE_CONSTANT (capacity_expr))
    {
      rust_error_at (expr_locus, "non const num copies %qT", array_type);
      return error_mark_node;
    }

  // get the compiled value
  tree translated_expr = CompileExpr::Compile (elems.get_elem_to_copy (), ctx);

  tree max_domain = TYPE_MAX_VALUE (domain);
  tree min_domain = TYPE_MIN_VALUE (domain);

  auto max = wi::to_offset (max_domain);
  auto min = wi::to_offset (min_domain);
  auto precision = TYPE_PRECISION (TREE_TYPE (domain));
  auto sign = TYPE_SIGN (TREE_TYPE (domain));
  unsigned HOST_WIDE_INT len
    = wi::ext (max - min + 1, precision, sign).to_uhwi ();

  // In a const context we must initialize the entire array, which entails
  // allocating for each element. If the user wants a huge array, we will OOM
  // and die horribly.
  if (ctx->const_context_p ())
    {
      size_t idx = 0;
      std::vector<unsigned long> indexes;
      std::vector<tree> constructor;
      for (unsigned HOST_WIDE_INT i = 0; i < len; i++)
	{
	  constructor.push_back (translated_expr);
	  indexes.push_back (idx++);
	}

      return ctx->get_backend ()->array_constructor_expression (array_type,
								indexes,
								constructor,
								expr_locus);
    }

  else
    {
      // Create a new block scope in which to initialize the array
      tree fndecl = NULL_TREE;
      if (ctx->in_fn ())
	fndecl = ctx->peek_fn ().fndecl;

      std::vector<Bvariable *> locals;
      tree enclosing_scope = ctx->peek_enclosing_scope ();
      tree init_block
	= ctx->get_backend ()->block (fndecl, enclosing_scope, locals,
				      expr_locus, expr_locus);
      ctx->push_block (init_block);

      tree tmp;
      tree stmts
	= ctx->get_backend ()->array_initializer (fndecl, init_block,
						  array_type, capacity_expr,
						  translated_expr, &tmp,
						  expr_locus);
      ctx->add_statement (stmts);

      tree block = ctx->pop_block ();

      // The result is a compound expression which creates a temporary array,
      // initializes all the elements in a loop, and then yeilds the array.
      return ctx->get_backend ()->compound_expression (block, tmp, expr_locus);
    }
}

tree
HIRCompileBase::resolve_adjustements (
  std::vector<Resolver::Adjustment> &adjustments, tree expression,
  Location locus)
{
  tree e = expression;
  for (auto &adjustment : adjustments)
    {
      switch (adjustment.get_type ())
	{
	case Resolver::Adjustment::AdjustmentType::ERROR:
	  return error_mark_node;

	case Resolver::Adjustment::AdjustmentType::IMM_REF:
	  case Resolver::Adjustment::AdjustmentType::MUT_REF: {
	    if (!SLICE_TYPE_P (TREE_TYPE (e)))
	      {
		e = address_expression (e, locus);
	      }
	  }
	  break;

	case Resolver::Adjustment::AdjustmentType::DEREF:
	case Resolver::Adjustment::AdjustmentType::DEREF_MUT:
	  e = resolve_deref_adjustment (adjustment, e, locus);
	  break;

	case Resolver::Adjustment::AdjustmentType::INDIRECTION:
	  e = resolve_indirection_adjustment (adjustment, e, locus);
	  break;

	case Resolver::Adjustment::AdjustmentType::UNSIZE:
	  e = resolve_unsized_adjustment (adjustment, e, locus);
	  break;
	}
    }

  return e;
}

tree
HIRCompileBase::resolve_deref_adjustment (Resolver::Adjustment &adjustment,
					  tree expression, Location locus)
{
  rust_assert (adjustment.is_deref_adjustment ()
	       || adjustment.is_deref_mut_adjustment ());
  rust_assert (adjustment.has_operator_overload ());

  TyTy::FnType *lookup = adjustment.get_deref_operator_fn ();
  HIR::ImplItem *resolved_item = adjustment.get_deref_hir_item ();

  tree fn_address = error_mark_node;
  if (!lookup->has_subsititions_defined ())
    fn_address = CompileInherentImplItem::Compile (resolved_item, ctx, nullptr,
						   true, locus);
  else
    fn_address = CompileInherentImplItem::Compile (resolved_item, ctx, lookup,
						   true, locus);

  // does it need a reference to call
  tree adjusted_argument = expression;
  bool needs_borrow = adjustment.get_deref_adjustment_type ()
		      != Resolver::Adjustment::AdjustmentType::ERROR;
  if (needs_borrow)
    {
      adjusted_argument = address_expression (expression, locus);
    }

  // make the call
  return ctx->get_backend ()->call_expression (fn_address, {adjusted_argument},
					       nullptr, locus);
}

tree
HIRCompileBase::resolve_indirection_adjustment (
  Resolver::Adjustment &adjustment, tree expression, Location locus)
{
  return indirect_expression (expression, locus);
}

tree
HIRCompileBase::resolve_unsized_adjustment (Resolver::Adjustment &adjustment,
					    tree expression, Location locus)
{
  bool expect_slice
    = adjustment.get_expected ()->get_kind () == TyTy::TypeKind::SLICE;
  bool expect_dyn
    = adjustment.get_expected ()->get_kind () == TyTy::TypeKind::DYNAMIC;

  // assumes this is an array
  tree expr_type = TREE_TYPE (expression);
  if (expect_slice)
    {
      rust_assert (TREE_CODE (expr_type) == ARRAY_TYPE);
      return resolve_unsized_slice_adjustment (adjustment, expression, locus);
    }

  rust_assert (expect_dyn);
  return resolve_unsized_dyn_adjustment (adjustment, expression, locus);
}

tree
HIRCompileBase::resolve_unsized_slice_adjustment (
  Resolver::Adjustment &adjustment, tree expression, Location locus)
{
  // assumes this is an array
  tree expr_type = TREE_TYPE (expression);
  rust_assert (TREE_CODE (expr_type) == ARRAY_TYPE);

  // takes an array and returns a fat-pointer so this becomes a constructor
  // expression
  rust_assert (adjustment.get_expected ()->get_kind ()
	       == TyTy::TypeKind::SLICE);
  tree fat_pointer
    = TyTyResolveCompile::compile (ctx, adjustment.get_expected ());

  // make a constructor for this
  tree data = address_expression (expression, locus);

  // fetch the size from the domain
  tree domain = TYPE_DOMAIN (expr_type);
  unsigned HOST_WIDE_INT array_size
    = wi::ext (wi::to_offset (TYPE_MAX_VALUE (domain))
		 - wi::to_offset (TYPE_MIN_VALUE (domain)) + 1,
	       TYPE_PRECISION (TREE_TYPE (domain)),
	       TYPE_SIGN (TREE_TYPE (domain)))
	.to_uhwi ();
  tree size = build_int_cstu (size_type_node, array_size);

  return ctx->get_backend ()->constructor_expression (fat_pointer, false,
						      {data, size}, -1, locus);
}

tree
HIRCompileBase::resolve_unsized_dyn_adjustment (
  Resolver::Adjustment &adjustment, tree expression, Location locus)
{
  tree rvalue = expression;
  Location rvalue_locus = locus;

  const TyTy::BaseType *actual = adjustment.get_actual ();
  const TyTy::BaseType *expected = adjustment.get_expected ();

  const TyTy::DynamicObjectType *dyn
    = static_cast<const TyTy::DynamicObjectType *> (expected);

  rust_debug ("resolve_unsized_dyn_adjustment actual={%s} dyn={%s}",
	      actual->debug_str ().c_str (), dyn->debug_str ().c_str ());

  return coerce_to_dyn_object (rvalue, actual, dyn, rvalue_locus);
}

void
CompileExpr::visit (HIR::RangeFromToExpr &expr)
{
  tree from = CompileExpr::Compile (expr.get_from_expr ().get (), ctx);
  tree to = CompileExpr::Compile (expr.get_to_expr ().get (), ctx);
  if (from == error_mark_node || to == error_mark_node)
    {
      translated = error_mark_node;
      return;
    }

  TyTy::BaseType *tyty = nullptr;
  bool ok
    = ctx->get_tyctx ()->lookup_type (expr.get_mappings ().get_hirid (), &tyty);
  rust_assert (ok);

  tree adt = TyTyResolveCompile::compile (ctx, tyty);

  // make the constructor
  translated
    = ctx->get_backend ()->constructor_expression (adt, false, {from, to}, -1,
						   expr.get_locus ());
}

void
CompileExpr::visit (HIR::RangeFromExpr &expr)
{
  tree from = CompileExpr::Compile (expr.get_from_expr ().get (), ctx);
  if (from == error_mark_node)
    {
      translated = error_mark_node;
      return;
    }

  TyTy::BaseType *tyty = nullptr;
  bool ok
    = ctx->get_tyctx ()->lookup_type (expr.get_mappings ().get_hirid (), &tyty);
  rust_assert (ok);

  tree adt = TyTyResolveCompile::compile (ctx, tyty);

  // make the constructor
  translated
    = ctx->get_backend ()->constructor_expression (adt, false, {from}, -1,
						   expr.get_locus ());
}

void
CompileExpr::visit (HIR::RangeToExpr &expr)
{
  tree to = CompileExpr::Compile (expr.get_to_expr ().get (), ctx);
  if (to == error_mark_node)
    {
      translated = error_mark_node;
      return;
    }

  TyTy::BaseType *tyty = nullptr;
  bool ok
    = ctx->get_tyctx ()->lookup_type (expr.get_mappings ().get_hirid (), &tyty);
  rust_assert (ok);

  tree adt = TyTyResolveCompile::compile (ctx, tyty);

  // make the constructor
  translated
    = ctx->get_backend ()->constructor_expression (adt, false, {to}, -1,
						   expr.get_locus ());
}

void
CompileExpr::visit (HIR::RangeFullExpr &expr)
{
  TyTy::BaseType *tyty = nullptr;
  bool ok
    = ctx->get_tyctx ()->lookup_type (expr.get_mappings ().get_hirid (), &tyty);
  rust_assert (ok);

  tree adt = TyTyResolveCompile::compile (ctx, tyty);
  translated = ctx->get_backend ()->constructor_expression (adt, false, {}, -1,
							    expr.get_locus ());
}

void
CompileExpr::visit (HIR::RangeFromToInclExpr &expr)
{
  tree from = CompileExpr::Compile (expr.get_from_expr ().get (), ctx);
  tree to = CompileExpr::Compile (expr.get_to_expr ().get (), ctx);
  if (from == error_mark_node || to == error_mark_node)
    {
      translated = error_mark_node;
      return;
    }

  TyTy::BaseType *tyty = nullptr;
  bool ok
    = ctx->get_tyctx ()->lookup_type (expr.get_mappings ().get_hirid (), &tyty);
  rust_assert (ok);

  tree adt = TyTyResolveCompile::compile (ctx, tyty);

  // make the constructor
  translated
    = ctx->get_backend ()->constructor_expression (adt, false, {from, to}, -1,
						   expr.get_locus ());
}

void
CompileExpr::visit (HIR::ArrayIndexExpr &expr)
{
  tree array_reference = CompileExpr::Compile (expr.get_array_expr (), ctx);
  tree index = CompileExpr::Compile (expr.get_index_expr (), ctx);

  // this might be an core::ops::index lang item situation
  TyTy::FnType *fntype;
  bool is_op_overload = ctx->get_tyctx ()->lookup_operator_overload (
    expr.get_mappings ().get_hirid (), &fntype);
  if (is_op_overload)
    {
      auto lang_item_type = Analysis::RustLangItem::ItemType::INDEX;
      tree operator_overload_call
	= resolve_operator_overload (lang_item_type, expr, array_reference,
				     index, expr.get_array_expr (),
				     expr.get_index_expr ());

      tree actual_type = TREE_TYPE (operator_overload_call);
      bool can_indirect = TYPE_PTR_P (actual_type) || TYPE_REF_P (actual_type);
      if (!can_indirect)
	{
	  // nothing to do
	  translated = operator_overload_call;
	  return;
	}

      // rust deref always returns a reference from this overload then we can
      // actually do the indirection
      translated
	= indirect_expression (operator_overload_call, expr.get_locus ());
      return;
    }

  // lets check if the array is a reference type then we can add an
  // indirection if required
  TyTy::BaseType *array_expr_ty = nullptr;
  bool ok = ctx->get_tyctx ()->lookup_type (
    expr.get_array_expr ()->get_mappings ().get_hirid (), &array_expr_ty);
  rust_assert (ok);

  // do we need to add an indirect reference
  if (array_expr_ty->get_kind () == TyTy::TypeKind::REF)
    {
      array_reference
	= indirect_expression (array_reference, expr.get_locus ());
    }

  translated
    = ctx->get_backend ()->array_index_expression (array_reference, index,
						   expr.get_locus ());
}

void
CompileExpr::visit (HIR::ClosureExpr &expr)
{
  TyTy::BaseType *closure_expr_ty = nullptr;
  if (!ctx->get_tyctx ()->lookup_type (expr.get_mappings ().get_hirid (),
				       &closure_expr_ty))
    {
      rust_fatal_error (expr.get_locus (),
			"did not resolve type for this ClosureExpr");
      return;
    }
  rust_assert (closure_expr_ty->get_kind () == TyTy::TypeKind::CLOSURE);
  TyTy::ClosureType *closure_tyty
    = static_cast<TyTy::ClosureType *> (closure_expr_ty);
  tree compiled_closure_tyty = TyTyResolveCompile::compile (ctx, closure_tyty);

  // generate closure function
  generate_closure_function (expr, *closure_tyty, compiled_closure_tyty);

  // lets ignore state capture for now we need to instantiate the struct anyway
  // then generate the function
  std::vector<tree> vals;
  for (const auto &capture : closure_tyty->get_captures ())
    {
      // lookup the HirId
      HirId ref = UNKNOWN_HIRID;
      bool ok = ctx->get_mappings ()->lookup_node_to_hir (capture, &ref);
      rust_assert (ok);

      // lookup the var decl
      Bvariable *var = nullptr;
      bool found = ctx->lookup_var_decl (ref, &var);
      rust_assert (found);

      // FIXME
      // this should bes based on the closure move-ability
      tree var_expr = var->get_tree (expr.get_locus ());
      tree val = address_expression (var_expr, expr.get_locus ());
      vals.push_back (val);
    }

  translated
    = ctx->get_backend ()->constructor_expression (compiled_closure_tyty, false,
						   vals, -1, expr.get_locus ());
}

tree
CompileExpr::generate_closure_function (HIR::ClosureExpr &expr,
					TyTy::ClosureType &closure_tyty,
					tree compiled_closure_tyty)
{
  TyTy::FnType *fn_tyty = nullptr;
  tree compiled_fn_type
    = generate_closure_fntype (expr, closure_tyty, compiled_closure_tyty,
			       &fn_tyty);
  if (compiled_fn_type == error_mark_node)
    return error_mark_node;

  const Resolver::CanonicalPath &parent_canonical_path
    = closure_tyty.get_ident ().path;
  Resolver::CanonicalPath path = parent_canonical_path.append (
    Resolver::CanonicalPath::new_seg (UNKNOWN_NODEID, "{{closure}}"));

  std::string ir_symbol_name = path.get ();
  std::string asm_name = ctx->mangle_item (&closure_tyty, path);

  unsigned int flags = 0;
  tree fndecl
    = ctx->get_backend ()->function (compiled_fn_type, ir_symbol_name, asm_name,
				     flags, expr.get_locus ());

  // insert into the context
  ctx->insert_function_decl (fn_tyty, fndecl);
  ctx->insert_closure_decl (&closure_tyty, fndecl);

  // setup the parameters
  std::vector<Bvariable *> param_vars;

  // closure self
  Bvariable *self_param
    = ctx->get_backend ()->parameter_variable (fndecl, "$closure",
					       compiled_closure_tyty,
					       expr.get_locus ());
  DECL_ARTIFICIAL (self_param->get_decl ()) = 1;
  param_vars.push_back (self_param);

  // push a new context
  ctx->push_closure_context (expr.get_mappings ().get_hirid ());

  // setup the implicit argument captures
  size_t idx = 0;
  for (const auto &capture : closure_tyty.get_captures ())
    {
      // lookup the HirId
      HirId ref = UNKNOWN_HIRID;
      bool ok = ctx->get_mappings ()->lookup_node_to_hir (capture, &ref);
      rust_assert (ok);

      // get the assessor
      tree binding = ctx->get_backend ()->struct_field_expression (
	self_param->get_tree (expr.get_locus ()), idx, expr.get_locus ());
      tree indirection = indirect_expression (binding, expr.get_locus ());

      // insert bindings
      ctx->insert_closure_binding (ref, indirection);

      // continue
      idx++;
    }

  // args tuple
  tree args_type
    = TyTyResolveCompile::compile (ctx, &closure_tyty.get_parameters ());
  Bvariable *args_param
    = ctx->get_backend ()->parameter_variable (fndecl, "args", args_type,
					       expr.get_locus ());
  param_vars.push_back (args_param);

  // setup the implicit mappings for the arguments. Since argument passing to
  // closure functions is done via passing a tuple but the closure body expects
  // just normal arguments this means we need to destructure them similar to
  // what we do in MatchExpr's. This means when we have a closure-param of a we
  // actually setup the destructure to take from the args tuple

  tree args_param_expr = args_param->get_tree (expr.get_locus ());
  size_t i = 0;
  for (auto &closure_param : expr.get_params ())
    {
      tree compiled_param_var = ctx->get_backend ()->struct_field_expression (
	args_param_expr, i, closure_param.get_locus ());

      const HIR::Pattern &param_pattern = *closure_param.get_pattern ();
      ctx->insert_pattern_binding (
	param_pattern.get_pattern_mappings ().get_hirid (), compiled_param_var);
      i++;
    }

  if (!ctx->get_backend ()->function_set_parameters (fndecl, param_vars))
    {
      ctx->pop_closure_context ();
      return error_mark_node;
    }

  // lookup locals
  HIR::Expr *function_body = expr.get_expr ().get ();
  auto body_mappings = function_body->get_mappings ();
  Resolver::Rib *rib = nullptr;
  bool ok
    = ctx->get_resolver ()->find_name_rib (body_mappings.get_nodeid (), &rib);
  rust_assert (ok);

  std::vector<Bvariable *> locals
    = compile_locals_for_block (ctx, *rib, fndecl);

  tree enclosing_scope = NULL_TREE;
  Location start_location = function_body->get_locus ();
  Location end_location = function_body->get_locus ();
  bool is_block_expr
    = function_body->get_expression_type () == HIR::Expr::ExprType::Block;
  if (is_block_expr)
    {
      HIR::BlockExpr *body = static_cast<HIR::BlockExpr *> (function_body);
      start_location = body->get_locus ();
      end_location = body->get_end_locus ();
    }

  tree code_block = ctx->get_backend ()->block (fndecl, enclosing_scope, locals,
						start_location, end_location);
  ctx->push_block (code_block);

  TyTy::BaseType *tyret = &closure_tyty.get_result_type ();
  bool function_has_return = !closure_tyty.get_result_type ().is_unit ();
  Bvariable *return_address = nullptr;
  if (function_has_return)
    {
      tree return_type = TyTyResolveCompile::compile (ctx, tyret);

      bool address_is_taken = false;
      tree ret_var_stmt = NULL_TREE;

      return_address = ctx->get_backend ()->temporary_variable (
	fndecl, code_block, return_type, NULL, address_is_taken,
	expr.get_locus (), &ret_var_stmt);

      ctx->add_statement (ret_var_stmt);
    }

  ctx->push_fn (fndecl, return_address);

  if (is_block_expr)
    {
      HIR::BlockExpr *body = static_cast<HIR::BlockExpr *> (function_body);
      compile_function_body (ctx, fndecl, *body, true);
    }
  else
    {
      tree value = CompileExpr::Compile (function_body, ctx);
      tree return_expr
	= ctx->get_backend ()->return_statement (fndecl, {value},
						 function_body->get_locus ());
      ctx->add_statement (return_expr);
    }

  tree bind_tree = ctx->pop_block ();

  gcc_assert (TREE_CODE (bind_tree) == BIND_EXPR);
  DECL_SAVED_TREE (fndecl) = bind_tree;

  ctx->pop_closure_context ();
  ctx->pop_fn ();
  ctx->push_function (fndecl);

  return fndecl;
}

tree
CompileExpr::generate_closure_fntype (HIR::ClosureExpr &expr,
				      const TyTy::ClosureType &closure_tyty,
				      tree compiled_closure_tyty,
				      TyTy::FnType **fn_tyty)
{
  // grab the specified_bound
  rust_assert (closure_tyty.num_specified_bounds () == 1);
  const TyTy::TypeBoundPredicate &predicate
    = *closure_tyty.get_specified_bounds ().begin ();

  // ensure the fn_once_output associated type is set
  closure_tyty.setup_fn_once_output ();

  // the function signature is based on the trait bound that the closure
  // implements which is determined at the type resolution time
  //
  // https://github.com/rust-lang/rust/blob/7807a694c2f079fd3f395821bcc357eee8650071/library/core/src/ops/function.rs#L54-L71

  TyTy::TypeBoundPredicateItem item = TyTy::TypeBoundPredicateItem::error ();
  if (predicate.get_name ().compare ("FnOnce") == 0)
    {
      item = predicate.lookup_associated_item ("call_once");
    }
  else if (predicate.get_name ().compare ("FnMut") == 0)
    {
      item = predicate.lookup_associated_item ("call_mut");
    }
  else if (predicate.get_name ().compare ("Fn") == 0)
    {
      item = predicate.lookup_associated_item ("call");
    }
  else
    {
      // FIXME error message?
      gcc_unreachable ();
      return error_mark_node;
    }

  rust_assert (!item.is_error ());

  TyTy::BaseType *item_tyty = item.get_tyty_for_receiver (&closure_tyty);
  rust_assert (item_tyty->get_kind () == TyTy::TypeKind::FNDEF);
  *fn_tyty = static_cast<TyTy::FnType *> (item_tyty);
  return TyTyResolveCompile::compile (ctx, item_tyty);
}

bool
CompileExpr::generate_possible_fn_trait_call (HIR::CallExpr &expr,
					      tree receiver, tree *result)
{
  TyTy::FnType *fn_sig = nullptr;
  bool found_overload = ctx->get_tyctx ()->lookup_operator_overload (
    expr.get_mappings ().get_hirid (), &fn_sig);
  if (!found_overload)
    return false;

  auto id = fn_sig->get_ty_ref ();
  auto dId = fn_sig->get_id ();

  tree function = error_mark_node;
  bool found_closure = ctx->lookup_function_decl (id, &function, dId, fn_sig);
  if (!found_closure)
    {
      // something went wrong we still return true as this was meant to be an fn
      // trait call
      *result = error_mark_node;
      return true;
    }

  // need to apply any autoderef's to the self argument
  HirId autoderef_mappings_id = expr.get_mappings ().get_hirid ();
  std::vector<Resolver::Adjustment> *adjustments = nullptr;
  bool ok = ctx->get_tyctx ()->lookup_autoderef_mappings (autoderef_mappings_id,
							  &adjustments);
  rust_assert (ok);

  // apply adjustments for the fn call
  tree self = resolve_adjustements (*adjustments, receiver, expr.get_locus ());

  // resolve the arguments
  std::vector<tree> tuple_arg_vals;
  for (auto &argument : expr.get_arguments ())
    {
      auto rvalue = CompileExpr::Compile (argument.get (), ctx);
      tuple_arg_vals.push_back (rvalue);
    }

  // this is always the 2nd argument in the function signature
  tree fnty = TREE_TYPE (function);
  tree fn_arg_tys = TYPE_ARG_TYPES (fnty);
  tree tuple_args_tyty_chain = TREE_CHAIN (fn_arg_tys);
  tree tuple_args_tyty = TREE_VALUE (tuple_args_tyty_chain);

  tree tuple_args
    = ctx->get_backend ()->constructor_expression (tuple_args_tyty, false,
						   tuple_arg_vals, -1,
						   expr.get_locus ());

  // args are always self, and the tuple of the args we are passing where
  // self is the path of the call-expr in this case the fn_address
  std::vector<tree> args;
  args.push_back (self);
  args.push_back (tuple_args);

  tree call_address = address_expression (function, expr.get_locus ());
  *result = ctx->get_backend ()->call_expression (call_address, args,
						  nullptr /* static chain ?*/,
						  expr.get_locus ());
  return true;
}

} // namespace Compile
} // namespace Rust
